@@ -11,3 +11,4 @@ com.sebaslab.svelto.ecs/obj/ | |||
.vs/Svelto.ECS/DesignTimeBuild/.dtbcache.v2 | |||
.vs/Svelto.ECS/v16/.suo | |||
com.sebaslab.svelto.ecs/bin/ | |||
com.sebaslab.svelto.ecs/Svelto.ECS.sdpkg.user |
@@ -1 +1 @@ | |||
Subproject commit 4062f995396f9d34f8a58c7646f819c315356775 | |||
Subproject commit 8a1924de0f6017ccc03396b45a3ee78ae9195dac |
@@ -29,7 +29,7 @@ namespace Svelto.ECS | |||
.FastConcat(" previous operation was: ") | |||
.FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); | |||
if (_idChecker.TryGetValue((uint) egid.groupID, out var hash)) | |||
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) | |||
@@ -57,7 +57,7 @@ namespace Svelto.ECS | |||
.FastConcat(" previous operation was: ") | |||
.FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); | |||
var hash = _idChecker.GetOrCreate((uint) egid.groupID, () => new HashSet<uint>()); | |||
var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet<uint>()); | |||
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) | |||
@@ -67,13 +67,12 @@ namespace Svelto.ECS | |||
: "not available")); | |||
hash.Add(egid.entityID); | |||
_multipleOperationOnSameEGIDChecker.Add(egid, 1); | |||
} | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
#endif | |||
void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove((uint)groupID); } | |||
void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove(groupID); } | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
@@ -81,6 +80,6 @@ namespace Svelto.ECS | |||
void ClearChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); } | |||
readonly FasterDictionary<EGID, uint> _multipleOperationOnSameEGIDChecker = new FasterDictionary<EGID, uint>(); | |||
readonly FasterDictionary<uint, HashSet<uint>> _idChecker = new FasterDictionary<uint, HashSet<uint>>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, HashSet<uint>> _idChecker = new FasterDictionary<ExclusiveGroupStruct, HashSet<uint>>(); | |||
} | |||
} |
@@ -9,11 +9,11 @@ namespace Svelto.ECS | |||
[Serialization.DoNotSerialize] | |||
[Serializable] | |||
[StructLayout(LayoutKind.Explicit)] | |||
public struct EGID:IEquatable<EGID>,IComparable<EGID> | |||
public struct EGID : IEquatable<EGID>, IComparable<EGID> | |||
{ | |||
[FieldOffset(0)] public readonly uint entityID; | |||
[FieldOffset(4)] public readonly ExclusiveGroupStruct groupID; | |||
[FieldOffset(0)] readonly ulong _GID; | |||
[FieldOffset(0)] readonly ulong _GID; | |||
public static bool operator ==(EGID obj1, EGID obj2) | |||
{ | |||
@@ -27,17 +27,17 @@ namespace Svelto.ECS | |||
public EGID(uint entityID, ExclusiveGroupStruct groupID) : this() | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (groupID == (ExclusiveGroupStruct)default) | |||
throw new Exception("Trying to use a not initialised group ID"); | |||
#endif | |||
_GID = MAKE_GLOBAL_ID(entityID, (uint) groupID); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (groupID == (ExclusiveGroupStruct) default) | |||
throw new Exception("Trying to use a not initialised group ID"); | |||
#endif | |||
_GID = MAKE_GLOBAL_ID(entityID, groupID.ToIDAndBitmask()); | |||
} | |||
static ulong MAKE_GLOBAL_ID(uint entityId, uint groupId) | |||
{ | |||
return (ulong)groupId << 32 | ((ulong)entityId & 0xFFFFFFFF); | |||
return (ulong) groupId << 32 | ((ulong) entityId & 0xFFFFFFFF); | |||
} | |||
public static explicit operator uint(EGID id) | |||
@@ -46,7 +46,10 @@ namespace Svelto.ECS | |||
} | |||
//in the way it's used, ulong must be always the same for each id/group | |||
public static explicit operator ulong(EGID id) { return id._GID; } | |||
public static explicit operator ulong(EGID id) | |||
{ | |||
return id._GID; | |||
} | |||
public bool Equals(EGID other) | |||
{ | |||
@@ -72,7 +75,7 @@ namespace Svelto.ECS | |||
{ | |||
return _GID.CompareTo(other._GID); | |||
} | |||
internal EGID(uint entityID, uint groupID) : this() | |||
{ | |||
_GID = MAKE_GLOBAL_ID(entityID, groupID); | |||
@@ -81,14 +84,14 @@ namespace Svelto.ECS | |||
public override string ToString() | |||
{ | |||
var value = groupID.ToName(); | |||
return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(value); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public EntityReference ToEntityReference(EntitiesDB entitiesDB) | |||
{ | |||
return entitiesDB.GetEntityReference(this); | |||
} | |||
} | |||
} | |||
} |
@@ -7,173 +7,127 @@ namespace Svelto.ECS | |||
{ | |||
internal class DoubleBufferedEntitiesToAdd | |||
{ | |||
const int MaximumNumberOfItemsPerFrameBeforeToClear = 100; | |||
const int MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR = 100; | |||
internal void Swap() | |||
public DoubleBufferedEntitiesToAdd() | |||
{ | |||
Swap(ref current, ref other); | |||
Swap(ref currentEntitiesCreatedPerGroup, ref otherEntitiesCreatedPerGroup); | |||
} | |||
_currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; | |||
_otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; | |||
void Swap<T>(ref T item1, ref T item2) | |||
{ | |||
var toSwap = item2; | |||
item2 = item1; | |||
item1 = toSwap; | |||
current = _entityComponentsToAddBufferA; | |||
other = _entityComponentsToAddBufferB; | |||
} | |||
public void ClearOther() | |||
{ | |||
//do not clear the groups created so far, they will be reused, unless they are too many! | |||
var otherCount = other.count; | |||
if (otherCount > MaximumNumberOfItemsPerFrameBeforeToClear) | |||
if (otherCount > MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR) | |||
{ | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary>[] otherValuesArray = other.unsafeValues; | |||
for (int i = 0; i < otherCount; ++i) | |||
var otherValuesArray = other.unsafeValues; | |||
for (var i = 0; i < otherCount; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
ITypeSafeDictionary[] safeDictionaries = otherValuesArray[i].unsafeValues; | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
var safeDictionaries = otherValuesArray[i].unsafeValues; | |||
{ | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
} | |||
} | |||
//reset the number of entities created so far | |||
otherEntitiesCreatedPerGroup.FastClear(); | |||
_otherEntitiesCreatedPerGroup.FastClear(); | |||
other.FastClear(); | |||
return; | |||
} | |||
{ | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary>[] otherValuesArray = other.unsafeValues; | |||
for (int i = 0; i < otherCount; ++i) | |||
var otherValuesArray = other.unsafeValues; | |||
for (var i = 0; i < otherCount; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
ITypeSafeDictionary[] safeDictionaries = otherValuesArray[i].unsafeValues; | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
var safeDictionaries = otherValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
if (safeDictionariesCount <= MaximumNumberOfItemsPerFrameBeforeToClear) | |||
if (safeDictionariesCount <= MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR) | |||
{ | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].FastClear(); | |||
} | |||
} | |||
else | |||
{ | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
otherValuesArray[i].FastClear(); | |||
} | |||
} | |||
//reset the number of entities created so far | |||
otherEntitiesCreatedPerGroup.FastClear(); | |||
_otherEntitiesCreatedPerGroup.FastClear(); | |||
} | |||
} | |||
//Before I tried for the third time to use a SparseSet instead of FasterDictionary, remember that | |||
//while group indices are sequential, they may not be used in a sequential order. Sparseset needs | |||
//entities to be created sequentially (the index cannot be managed externally) | |||
internal FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> current; | |||
internal FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> other; | |||
/// <summary> | |||
/// To avoid extra allocation, I don't clear the groups, so I need an extra data structure | |||
/// to keep count of the number of entities built this frame. At the moment the actual number | |||
/// of entities built is not used | |||
/// </summary> | |||
FasterDictionary<ExclusiveGroupStruct, uint> currentEntitiesCreatedPerGroup; | |||
FasterDictionary<ExclusiveGroupStruct, uint> otherEntitiesCreatedPerGroup; | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
_entityComponentsToAddBufferA = | |||
new FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
_entityComponentsToAddBufferB = | |||
new FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, uint> _entitiesCreatedPerGroupA = new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, uint> _entitiesCreatedPerGroupB = new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
public DoubleBufferedEntitiesToAdd() | |||
{ | |||
currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; | |||
otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; | |||
current = _entityComponentsToAddBufferA; | |||
other = _entityComponentsToAddBufferB; | |||
} | |||
public void Dispose() | |||
{ | |||
{ | |||
var otherValuesArray = other.unsafeValues; | |||
for (int i = 0; i < other.count; ++i) | |||
for (var i = 0; i < other.count; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
var safeDictionaries = otherValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
} | |||
} | |||
{ | |||
var currentValuesArray = current.unsafeValues; | |||
for (int i = 0; i < current.count; ++i) | |||
for (var i = 0; i < current.count; ++i) | |||
{ | |||
var safeDictionariesCount = currentValuesArray[i].count; | |||
var safeDictionaries = currentValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
} | |||
} | |||
} | |||
internal void IncrementEntityCount(ExclusiveGroupStruct groupID) | |||
internal bool AnyEntityCreated() | |||
{ | |||
currentEntitiesCreatedPerGroup.GetOrCreate(groupID)++; | |||
return _currentEntitiesCreatedPerGroup.count > 0; | |||
} | |||
internal bool AnyEntityCreated() | |||
internal bool AnyOtherEntityCreated() | |||
{ | |||
return currentEntitiesCreatedPerGroup.count > 0; | |||
return _otherEntitiesCreatedPerGroup.count > 0; | |||
} | |||
internal bool AnyOtherEntityCreated() | |||
internal void IncrementEntityCount(ExclusiveGroupStruct groupID) | |||
{ | |||
return otherEntitiesCreatedPerGroup.count > 0; | |||
_currentEntitiesCreatedPerGroup.GetOrCreate(groupID)++; | |||
} | |||
internal void Preallocate | |||
(ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild) | |||
{ | |||
void PreallocateDictionaries(FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> fasterDictionary1) | |||
void PreallocateDictionaries | |||
(FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> dic) | |||
{ | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> group = | |||
fasterDictionary1.GetOrCreate((uint) groupID, () => new FasterDictionary<RefWrapperType, ITypeSafeDictionary>()); | |||
var group = dic.GetOrCreate(groupID, () => new FasterDictionary<RefWrapperType, | |||
ITypeSafeDictionary>()); | |||
foreach (var componentBuilder in entityComponentsToBuild) | |||
{ | |||
var entityComponentType = componentBuilder.GetEntityComponentType(); | |||
var safeDictionary = @group.GetOrCreate(new RefWrapperType(entityComponentType) | |||
, () => componentBuilder.CreateDictionary(numberOfEntities)); | |||
var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType) | |||
, () => componentBuilder | |||
.CreateDictionary(numberOfEntities)); | |||
componentBuilder.Preallocate(safeDictionary, numberOfEntities); | |||
} | |||
} | |||
@@ -181,9 +135,48 @@ namespace Svelto.ECS | |||
PreallocateDictionaries(current); | |||
PreallocateDictionaries(other); | |||
currentEntitiesCreatedPerGroup.GetOrCreate(groupID); | |||
otherEntitiesCreatedPerGroup.GetOrCreate(groupID); | |||
_currentEntitiesCreatedPerGroup.GetOrCreate(groupID); | |||
_otherEntitiesCreatedPerGroup.GetOrCreate(groupID); | |||
} | |||
internal void Swap() | |||
{ | |||
Swap(ref current, ref other); | |||
Swap(ref _currentEntitiesCreatedPerGroup, ref _otherEntitiesCreatedPerGroup); | |||
} | |||
void Swap<T>(ref T item1, ref T item2) | |||
{ | |||
var toSwap = item2; | |||
item2 = item1; | |||
item1 = toSwap; | |||
} | |||
//Before I tried for the third time to use a SparseSet instead of FasterDictionary, remember that | |||
//while group indices are sequential, they may not be used in a sequential order. Sparseset needs | |||
//entities to be created sequentially (the index cannot be managed externally) | |||
internal FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> current; | |||
internal FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> other; | |||
readonly FasterDictionary<ExclusiveGroupStruct, uint> _entitiesCreatedPerGroupA = | |||
new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, uint> _entitiesCreatedPerGroupB = | |||
new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> _entityComponentsToAddBufferA = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> _entityComponentsToAddBufferB = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
/// <summary> | |||
/// To avoid extra allocation, I don't clear the groups, so I need an extra data structure | |||
/// to keep count of the number of entities built this frame. At the moment the actual number | |||
/// of entities built is not used | |||
/// </summary> | |||
FasterDictionary<ExclusiveGroupStruct, uint> _currentEntitiesCreatedPerGroup; | |||
FasterDictionary<ExclusiveGroupStruct, uint> _otherEntitiesCreatedPerGroup; | |||
} | |||
} | |||
} |
@@ -101,7 +101,7 @@ namespace Svelto.ECS | |||
try | |||
{ | |||
entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemoveOnDispose, profiler | |||
, new ExclusiveGroupStruct(groups.Key)); | |||
, groups.Key); | |||
} | |||
catch (Exception e) | |||
{ | |||
@@ -26,7 +26,7 @@ namespace Svelto.ECS | |||
{ | |||
CheckAddEntityID(entityID, descriptorType); | |||
DBC.ECS.Check.Require((uint)entityID.groupID != 0 | |||
DBC.ECS.Check.Require(entityID.groupID.isInvalid == false | |||
, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); | |||
var reference = _entityLocator.ClaimReference(); | |||
@@ -152,7 +152,7 @@ namespace Svelto.ECS | |||
var wrapper = new RefWrapperType(entityComponentType); | |||
ITypeSafeDictionary fromTypeSafeDictionary = | |||
GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, wrapper); | |||
GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
@@ -176,7 +176,7 @@ namespace Svelto.ECS | |||
{ | |||
//add all the entities | |||
var refWrapper = new RefWrapperType(entityComponentType); | |||
var fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, refWrapper); | |||
var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); | |||
ITypeSafeDictionary toEntitiesDictionary = null; | |||
if (toGroup != null) | |||
@@ -201,7 +201,7 @@ namespace Svelto.ECS | |||
using (sampler.Sample("RemoveEntityFromDictionary")) | |||
{ | |||
var refWrapper = new RefWrapperType(entityComponentType); | |||
var fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, refWrapper); | |||
var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); | |||
fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); | |||
} | |||
@@ -224,7 +224,7 @@ namespace Svelto.ECS | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup = GetDBGroup(fromIdGroupId); | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup = GetOrCreateDBGroup(toGroupId); | |||
_entityLocator.UpdateAllGroupReferenceLocators(fromIdGroupId, (uint) toGroupId); | |||
_entityLocator.UpdateAllGroupReferenceLocators(fromIdGroupId, toGroupId); | |||
foreach (var dictionaryOfEntities in fromGroup) | |||
{ | |||
@@ -235,12 +235,11 @@ namespace Svelto.ECS | |||
var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; | |||
var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId]; | |||
toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, (uint) toGroupId, this); | |||
toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, toGroupId, this); | |||
//call all the MoveTo callbacks | |||
dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap | |||
, dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId) | |||
, new ExclusiveGroupStruct(toGroupId), profiler); | |||
, dictionaryOfEntities.Value, fromIdGroupId, toGroupId, profiler); | |||
//todo: if it's unmanaged, I can use fastclear | |||
groupOfEntitiesToCopyAndClear.Clear(); | |||
@@ -254,7 +253,7 @@ namespace Svelto.ECS | |||
if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId | |||
, out FasterDictionary<RefWrapperType, ITypeSafeDictionary> | |||
fromGroup) == false) | |||
throw new ECSException("Group doesn't exist: ".FastConcat((uint) fromIdGroupId)); | |||
throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId.ToName())); | |||
return fromGroup; | |||
} | |||
@@ -289,11 +288,11 @@ namespace Svelto.ECS | |||
} | |||
static ITypeSafeDictionary GetTypeSafeDictionary | |||
(uint groupID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> @group, RefWrapperType refWrapper) | |||
(ExclusiveGroupStruct groupID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> @group, RefWrapperType refWrapper) | |||
{ | |||
if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) | |||
{ | |||
throw new ECSException("no group found: ".FastConcat(groupID)); | |||
throw new ECSException("no group found: ".FastConcat(groupID.ToName())); | |||
} | |||
return fromTypeSafeDictionary; | |||
@@ -308,7 +307,7 @@ namespace Svelto.ECS | |||
foreach (var dictionaryOfEntities in dictionariesOfEntities) | |||
{ | |||
dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler | |||
, new ExclusiveGroupStruct(groupID)); | |||
, groupID); | |||
dictionaryOfEntities.Value.FastClear(); | |||
var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; | |||
@@ -28,7 +28,7 @@ namespace Svelto.ECS | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntity<T>(EGID entityEGID) where T : IEntityDescriptor, new() | |||
{ | |||
DBC.ECS.Check.Require((uint)entityEGID.groupID != 0, "invalid group detected"); | |||
DBC.ECS.Check.Require(entityEGID.groupID.isInvalid == false, "invalid group detected"); | |||
var descriptorComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
_enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache<T>.type); | |||
@@ -40,7 +40,7 @@ namespace Svelto.ECS | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID) | |||
{ | |||
DBC.ECS.Check.Require((uint)groupID != 0, "invalid group detected"); | |||
DBC.ECS.Check.Require(groupID.isInvalid == false, "invalid group detected"); | |||
_enginesRoot.Target.RemoveGroupID(groupID); | |||
_enginesRoot.Target.QueueEntitySubmitOperation( | |||
@@ -108,7 +108,7 @@ namespace Svelto.ECS | |||
public void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup toGroupID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
SwapEntityGroup<T>(fromID, new EGID(fromID.entityID, (uint) toGroupID)); | |||
SwapEntityGroup<T>(fromID, new EGID(fromID.entityID, toGroupID)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
@@ -147,8 +147,8 @@ namespace Svelto.ECS | |||
public void SwapEntityGroup<T>(EGID fromID, EGID toID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
DBC.ECS.Check.Require((uint)fromID.groupID != 0, "invalid group detected"); | |||
DBC.ECS.Check.Require((uint)toID.groupID != 0, "invalid group detected"); | |||
DBC.ECS.Check.Require(fromID.groupID.isInvalid == false, "invalid group detected"); | |||
DBC.ECS.Check.Require(toID.groupID.isInvalid == false, "invalid group detected"); | |||
var enginesRootTarget = _enginesRoot.Target; | |||
var descriptorComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
@@ -97,7 +97,7 @@ namespace Svelto.ECS | |||
//each group is indexed by entity view type. for each type there is a dictionary indexed by entityID | |||
foreach (var groupToSubmit in _groupedEntityToAdd.other) | |||
{ | |||
var groupID = new ExclusiveGroupStruct(groupToSubmit.Key); | |||
var groupID = groupToSubmit.Key; | |||
var groupDB = GetOrCreateDBGroup(groupID); | |||
//add the entityComponents in the group | |||
@@ -111,7 +111,7 @@ namespace Svelto.ECS | |||
groupID, groupDB, wrapper, targetTypeSafeDictionary); | |||
//Fill the DB with the entity components generated this frame. | |||
dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, (uint) groupID, this); | |||
dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, groupID, this); | |||
} | |||
} | |||
} | |||
@@ -122,7 +122,7 @@ namespace Svelto.ECS | |||
{ | |||
foreach (var groupToSubmit in _groupedEntityToAdd.other) | |||
{ | |||
var groupID = new ExclusiveGroupStruct(groupToSubmit.Key); | |||
var groupID = groupToSubmit.Key; | |||
var groupDB = GetDBGroup(groupID); | |||
//entityComponentsToSubmit is the array of components found in the groupID per component type. | |||
//if there are N entities to submit, and M components type to add for each entity, this foreach will run NxM times. | |||
@@ -131,8 +131,7 @@ namespace Svelto.ECS | |||
var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)]; | |||
entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks( | |||
_reactiveEnginesAddRemove, realDic, null, new ExclusiveGroupStruct(groupID) | |||
, in profiler); | |||
_reactiveEnginesAddRemove, realDic, null, groupID, in profiler); | |||
numberOfOperations += entityComponentsToSubmit.Value.count; | |||
@@ -61,7 +61,7 @@ namespace Svelto.ECS | |||
//if the same group is found used with both T1 and T2 | |||
if (groupID == fasterDictionaryNodes2[j].key) | |||
{ | |||
result.Add(new ExclusiveGroupStruct(groupID)); | |||
result.Add(groupID); | |||
break; | |||
} | |||
} | |||
@@ -28,11 +28,11 @@ namespace Svelto.ECS.Internal | |||
static FasterDictionary<RefWrapperType, ITypeSafeDictionary> FetchEntityGroup | |||
(ExclusiveGroupStruct groupID, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType) | |||
{ | |||
if (groupEntityComponentsByType.current.TryGetValue((uint) groupID, out var group) == false) | |||
if (groupEntityComponentsByType.current.TryGetValue(groupID, out var group) == false) | |||
{ | |||
group = new FasterDictionary<RefWrapperType, ITypeSafeDictionary>(); | |||
groupEntityComponentsByType.current.Add((uint) groupID, group); | |||
groupEntityComponentsByType.current.Add(groupID, group); | |||
} | |||
//track the number of entities created so far in the group. | |||
@@ -49,9 +49,9 @@ namespace Svelto.ECS.Internal | |||
#endif | |||
) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if DEBUG && !PROFILE_SVELTO | |||
DBC.ECS.Check.Require(componentBuilders != null, $"Invalid Entity Descriptor {descriptorType}"); | |||
#endif | |||
#endif | |||
var numberOfComponents = componentBuilders.Length; | |||
#if DEBUG && !PROFILE_SVELTO | |||
@@ -81,7 +81,9 @@ namespace Svelto.ECS.Internal | |||
, IComponentBuilder componentBuilder, IEnumerable<object> implementors) | |||
{ | |||
var entityComponentType = componentBuilder.GetEntityComponentType(); | |||
var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType), (ref IComponentBuilder cb) => cb.CreateDictionary(1), ref componentBuilder); | |||
var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType) | |||
, (ref IComponentBuilder cb) => cb.CreateDictionary(1) | |||
, ref componentBuilder); | |||
//if the safeDictionary hasn't been created yet, it will be created inside this method. | |||
componentBuilder.BuildEntityAndAddToList(safeDictionary, entityID, implementors); | |||
@@ -129,7 +129,7 @@ namespace Svelto.ECS | |||
_egidToReferenceMap.Remove(groupId); | |||
} | |||
internal void UpdateAllGroupReferenceLocators(ExclusiveGroupStruct fromGroupId, uint toGroupId) | |||
internal void UpdateAllGroupReferenceLocators(ExclusiveGroupStruct fromGroupId, ExclusiveGroupStruct toGroupId) | |||
{ | |||
if (_egidToReferenceMap.TryGetValue(fromGroupId, out var groupMap) == false) | |||
return; | |||
@@ -11,7 +11,6 @@ namespace Svelto.ECS | |||
/// <summary> | |||
/// c# Static constructors are guaranteed to be thread safe | |||
/// </summary> | |||
public static void Init() | |||
{ | |||
List<Assembly> assemblies = AssemblyUtility.GetCompatibleAssemblies(); | |||
@@ -19,12 +18,13 @@ namespace Svelto.ECS | |||
{ | |||
try | |||
{ | |||
var typeOfExclusiveGroup = typeof(ExclusiveGroup); | |||
var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct); | |||
var typeOfExclusiveGroup = typeof(ExclusiveGroup); | |||
var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct); | |||
foreach (Type type in AssemblyUtility.GetTypesSafe(assembly)) | |||
{ | |||
if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //this means only static classes | |||
if (type != null && type.IsClass && type.IsSealed | |||
&& type.IsAbstract) //this means only static classes | |||
{ | |||
var fields = type.GetFields(); | |||
@@ -34,25 +34,27 @@ namespace Svelto.ECS | |||
{ | |||
if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType)) | |||
{ | |||
var group = (ExclusiveGroup)field.GetValue(null); | |||
var name = $"{type.FullName}.{field.Name}"; | |||
#if DEBUG | |||
GroupNamesMap.idToName[(uint)@group] = $"{name} {(uint)group})"; | |||
var group = (ExclusiveGroup) field.GetValue(null); | |||
#if DEBUG | |||
GroupNamesMap.idToName[@group] = | |||
$"{$"{type.FullName}.{field.Name}"} {group.id})"; | |||
#endif | |||
RegisterGroup(group, name); | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
RegisterGroup(group, $"{type.FullName}.{field.Name}"); | |||
} | |||
else | |||
{ | |||
if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) | |||
{ | |||
var group = (ExclusiveGroupStruct)field.GetValue(null); | |||
var name = $"{type.FullName}.{field.Name}"; | |||
#if DEBUG | |||
GroupNamesMap.idToName[(uint)@group] = $"{name} {(uint)group})"; | |||
var group = (ExclusiveGroupStruct) field.GetValue(null); | |||
#if DEBUG | |||
GroupNamesMap.idToName[@group] = | |||
$"{$"{type.FullName}.{field.Name}"} {group.id})"; | |||
#endif | |||
RegisterGroup(@group, name); | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
RegisterGroup(@group, $"{type.FullName}.{field.Name}"); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
@@ -61,45 +63,53 @@ namespace Svelto.ECS | |||
catch | |||
{ | |||
Console.LogDebugWarning( | |||
"something went wrong while gathering group names on the assembly: ".FastConcat(assembly.FullName)); | |||
"something went wrong while gathering group names on the assembly: ".FastConcat( | |||
assembly.FullName)); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// The hashname is independent from the actual group ID. this is fundamental because it is want | |||
/// guarantees the hash to be the same across different machines | |||
/// </summary> | |||
/// <param name="exclusiveGroupStruct"></param> | |||
/// <param name="name"></param> | |||
/// <exception cref="ECSException"></exception> | |||
public static void RegisterGroup(ExclusiveGroupStruct exclusiveGroupStruct, string name) | |||
{ | |||
//Group already registered by another field referencing the same group | |||
if (_hashByGroups.ContainsKey(exclusiveGroupStruct)) | |||
return; | |||
var nameHash = DesignatedHash.Hash(Encoding.ASCII.GetBytes(name)); | |||
if(_groupsByHash.ContainsKey(nameHash)) | |||
if (_groupsByHash.ContainsKey(nameHash)) | |||
throw new ECSException($"Group hash collision with {name} and {_groupsByHash[nameHash]}"); | |||
Console.LogDebug($"Registering group {name} with ID {(uint)exclusiveGroupStruct} to {nameHash}"); | |||
Console.LogDebug($"Registering group {name} with ID {exclusiveGroupStruct.id} to {nameHash}"); | |||
_groupsByHash.Add(nameHash, exclusiveGroupStruct); | |||
_hashByGroups.Add(exclusiveGroupStruct, nameHash); | |||
} | |||
public static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) | |||
{ | |||
#if DEBUG | |||
if (_hashByGroups.ContainsKey(groupStruct) == false) | |||
throw new ECSException($"Attempted to get hash from unregistered group {groupStruct}"); | |||
#endif | |||
return _hashByGroups[groupStruct]; | |||
} | |||
public static ExclusiveGroupStruct GetGroupFromHash(uint groupHash) | |||
{ | |||
#if DEBUG | |||
if (_groupsByHash.ContainsKey(groupHash) == false) | |||
throw new ECSException($"Attempted to get group from unregistered hash {groupHash}"); | |||
#endif | |||
return _groupsByHash[groupHash]; | |||
} | |||
@@ -4,23 +4,23 @@ using Svelto.ECS; | |||
static class GroupNamesMap | |||
{ | |||
#if DEBUG | |||
static GroupNamesMap() { idToName = new Dictionary<uint, string>(); } | |||
static GroupNamesMap() { idToName = new Dictionary<ExclusiveGroupStruct, string>(); } | |||
internal static readonly Dictionary<uint, string> idToName; | |||
internal static readonly Dictionary<ExclusiveGroupStruct, string> idToName; | |||
#endif | |||
#if DEBUG | |||
public static string ToName(this in ExclusiveGroupStruct group) | |||
{ | |||
var idToName = GroupNamesMap.idToName; | |||
if (idToName.TryGetValue((uint)@group, out var name) == false) | |||
name = $"<undefined:{((uint)group).ToString()}>"; | |||
Dictionary<ExclusiveGroupStruct, string> idToName = GroupNamesMap.idToName; | |||
if (idToName.TryGetValue(@group, out var name) == false) | |||
name = $"<undefined:{(group.id).ToString()}>"; | |||
return name; | |||
} | |||
#else | |||
public static string ToName(this in ExclusiveGroupStruct group) | |||
{ | |||
return ((uint)group).ToString(); | |||
return ((uint)group.ToIDAndBitmask()).ToString(); | |||
} | |||
#endif | |||
} |
@@ -19,12 +19,7 @@ namespace Svelto.ECS | |||
public static implicit operator ExclusiveGroupStruct(ExclusiveBuildGroup group) | |||
{ | |||
return new ExclusiveGroupStruct(group.group); | |||
} | |||
public static explicit operator uint(ExclusiveBuildGroup groupStruct) | |||
{ | |||
return (uint) groupStruct.@group; | |||
return group.group; | |||
} | |||
public override string ToString() | |||
@@ -32,6 +27,7 @@ namespace Svelto.ECS | |||
return this.group.ToName(); | |||
} | |||
internal ExclusiveGroupStruct @group { get; } | |||
internal ExclusiveGroupStruct @group { get; } | |||
public bool isInvalid => group == ExclusiveGroupStruct.Invalid; | |||
} | |||
} |
@@ -23,59 +23,49 @@ namespace Svelto.ECS | |||
{ | |||
public const uint MaxNumberOfExclusiveGroups = 2 << 20; | |||
public ExclusiveGroup(ExclusiveGroupBitmask bitmask = 0) | |||
public ExclusiveGroup() | |||
{ | |||
_group = ExclusiveGroupStruct.Generate((byte) bitmask); | |||
_group = ExclusiveGroupStruct.Generate(); | |||
} | |||
public ExclusiveGroup(string recognizeAs, ExclusiveGroupBitmask bitmask = 0) | |||
public ExclusiveGroup(string recognizeAs) | |||
{ | |||
_group = ExclusiveGroupStruct.Generate((byte) bitmask); | |||
_group = ExclusiveGroupStruct.Generate(); | |||
_knownGroups.Add(recognizeAs, _group); | |||
} | |||
public ExclusiveGroup(ExclusiveGroupBitmask bitmask) | |||
{ | |||
_group = ExclusiveGroupStruct.Generate((byte) bitmask); | |||
} | |||
public ExclusiveGroup(ushort range) | |||
{ | |||
_group = new ExclusiveGroupStruct(range); | |||
_group = ExclusiveGroupStruct.GenerateWithRange(range); | |||
#if DEBUG | |||
_range = range; | |||
#endif | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Disable() | |||
{ | |||
_group.Disable(); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Enable() | |||
{ | |||
_group.Enable(); | |||
} | |||
public static implicit operator ExclusiveGroupStruct(ExclusiveGroup group) | |||
{ | |||
return group._group; | |||
} | |||
public static explicit operator uint(ExclusiveGroup group) | |||
{ | |||
return (uint) @group._group; | |||
} | |||
public static ExclusiveGroupStruct operator +(ExclusiveGroup a, uint b) | |||
public static ExclusiveGroupStruct operator+(ExclusiveGroup @group, uint b) | |||
{ | |||
#if DEBUG | |||
if (a._range == 0) | |||
throw new ECSException($"Adding values to a not ranged ExclusiveGroup: {(uint) a}"); | |||
if (b >= a._range) | |||
throw new ECSException($"Using out of range group: {(uint) a} + {b}"); | |||
if (@group._range == 0) | |||
throw new ECSException($"Adding values to a not ranged ExclusiveGroup: {@group.id}"); | |||
if (b >= @group._range) | |||
throw new ECSException($"Using out of range group: {@group.id} + {b}"); | |||
#endif | |||
return a._group + b; | |||
return group._group + b; | |||
} | |||
public uint id => _group.id; | |||
//todo document the use case for this method | |||
public static ExclusiveGroupStruct Search(string holderGroupName) | |||
{ | |||
@@ -96,6 +86,6 @@ namespace Svelto.ECS | |||
#if DEBUG | |||
readonly ushort _range; | |||
#endif | |||
ExclusiveGroupStruct _group; | |||
readonly ExclusiveGroupStruct _group; | |||
} | |||
} |
@@ -1,26 +1,26 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using System.Runtime.InteropServices; | |||
using Svelto.DataStructures; | |||
using System.Threading; | |||
namespace Svelto.ECS | |||
{ | |||
[StructLayout(LayoutKind.Explicit, Size = 4)] | |||
//the type doesn't implement IEqualityComparer, what implements it is a custom comparer | |||
public struct ExclusiveGroupStruct : IEquatable<ExclusiveGroupStruct>, IComparable<ExclusiveGroupStruct> | |||
public readonly struct ExclusiveGroupStruct : IEquatable<ExclusiveGroupStruct>, IComparable<ExclusiveGroupStruct> | |||
{ | |||
public static readonly ExclusiveGroupStruct Invalid = default; //must stay here because of Burst | |||
public ExclusiveGroupStruct(byte[] data, uint pos):this() | |||
{ | |||
_id = (uint)( | |||
_idInternal = (uint)( | |||
data[pos] | |||
| data[++pos] << 8 | |||
| data[++pos] << 16 | |||
); | |||
_bytemask = (byte) (data[++pos] << 24); | |||
DBC.ECS.Check.Ensure(_id < _globalId, "Invalid group ID deserialiased"); | |||
DBC.ECS.Check.Ensure(id < _globalId, "Invalid group ID deserialiased"); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
@@ -32,7 +32,7 @@ namespace Svelto.ECS | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public override int GetHashCode() | |||
{ | |||
return (int) _id; | |||
return (int) id; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
@@ -50,86 +50,123 @@ namespace Svelto.ECS | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Equals(ExclusiveGroupStruct other) | |||
{ | |||
return other._id == _id; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if ((other.id != this.id || other._bytemask == this._bytemask) == false) | |||
throw new ECSException( | |||
"if the groups are correctly initialised, two groups with the same ID and different bitmask cannot exist"); | |||
#endif | |||
return other.id == this.id; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public int CompareTo(ExclusiveGroupStruct other) | |||
{ | |||
return other._id.CompareTo(_id); | |||
return other.id.CompareTo(id); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public readonly bool IsEnabled() | |||
public bool IsEnabled() | |||
{ | |||
return (_bytemask & (byte)ExclusiveGroupBitmask.DISABLED_BIT) == 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void Disable() | |||
{ | |||
_bytemask |= (byte)ExclusiveGroupBitmask.DISABLED_BIT; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void Enable() | |||
{ | |||
_bytemask &= (byte)(~ExclusiveGroupBitmask.DISABLED_BIT); | |||
} | |||
public override string ToString() | |||
{ | |||
return this.ToName(); | |||
} | |||
public bool isInvalid => this == Invalid; | |||
public uint id => _idInternal & 0xFFFFFF; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static explicit operator uint(ExclusiveGroupStruct groupStruct) | |||
{ | |||
return groupStruct._id; | |||
} | |||
public uint ToIDAndBitmask() => _idInternal; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b) | |||
{ | |||
var group = new ExclusiveGroupStruct {_id = a._id + b}; | |||
var aID = a.id + b; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (aID >= 0xFFFFFF) | |||
throw new IndexOutOfRangeException(); | |||
#endif | |||
var group = new ExclusiveGroupStruct(aID); | |||
return @group; | |||
} | |||
internal static ExclusiveGroupStruct Generate(byte bitmask = 0) | |||
internal static ExclusiveGroupStruct Generate() | |||
{ | |||
ExclusiveGroupStruct groupStruct; | |||
groupStruct._id = _globalId; | |||
groupStruct._bytemask = bitmask; | |||
DBC.ECS.Check.Require(_globalId + 1 < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); | |||
_globalId++; | |||
var newValue = Interlocked.Increment(ref _staticGlobalID); | |||
ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint) newValue - (uint) 1); | |||
DBC.ECS.Check.Require(_globalId < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); | |||
return groupStruct; | |||
} | |||
internal ExclusiveGroupStruct(ExclusiveGroupStruct @group):this() { this = group; } | |||
internal static ExclusiveGroupStruct Generate(byte bitmask) | |||
{ | |||
var newValue = Interlocked.Increment(ref _staticGlobalID); | |||
ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint) newValue - (uint) 1, bitmask); | |||
DBC.ECS.Check.Require(_globalId < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); | |||
return groupStruct; | |||
} | |||
/// <summary> | |||
/// Use this constructor to reserve N groups | |||
/// Use this to reserve N groups. We of course assign the current ID and then increment the index | |||
/// by range so that the next reserved index will take the range in consideration. This method is used | |||
/// internally by ExclusiveGroup. | |||
/// </summary> | |||
internal ExclusiveGroupStruct(ushort range):this() | |||
internal static ExclusiveGroupStruct GenerateWithRange(ushort range) | |||
{ | |||
_id = _globalId; | |||
DBC.ECS.Check.Require(_globalId + range < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); | |||
_globalId += range; | |||
var newValue = Interlocked.Add(ref _staticGlobalID, (int)range); | |||
ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint) newValue - (uint)range); | |||
DBC.ECS.Check.Require(_globalId < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); | |||
return groupStruct; | |||
} | |||
/// <summary> | |||
/// used internally only by the framework to convert uint in to groups. ID must be generated by the framework | |||
/// so only the framework can assure that this method is not being abused | |||
/// </summary> | |||
internal ExclusiveGroupStruct(uint groupID):this() | |||
{ | |||
DBC.ECS.Check.Require(groupID < 0xFFFFFF); | |||
_id = groupID; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (groupID >= 0xFFFFFF) | |||
throw new IndexOutOfRangeException(); | |||
#endif | |||
_idInternal = groupID; | |||
} | |||
ExclusiveGroupStruct(uint groupID, byte bytemask):this() | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (groupID >= 0xFFFFFF) | |||
throw new IndexOutOfRangeException(); | |||
#endif | |||
_idInternal = groupID; | |||
_bytemask = bytemask; | |||
} | |||
static ExclusiveGroupStruct() | |||
{ | |||
_staticGlobalID = 1; | |||
} | |||
[FieldOffset(0)] uint _id; | |||
[FieldOffset(3)] byte _bytemask; | |||
[FieldOffset(0)] readonly uint _idInternal; | |||
//byte mask can be used to add special flags to specific groups that can be checked for example when swapping groups | |||
//however at the moment we are not letting the user access it, because if we do so we should give access only to | |||
//4 bits are the other 4 bits will stay reserved for Svelto use (at the moment of writing using only the disable | |||
//bit) | |||
[FieldOffset(3)] readonly byte _bytemask; | |||
static uint _globalId = 1; //it starts from 1 because default EGID is considered not initialized value | |||
static int _staticGlobalID; | |||
static uint _globalId => (uint) _staticGlobalID; | |||
} | |||
} |
@@ -6,10 +6,10 @@ using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// This mechanism is not for thread-safety but to be sure that all the permutations of group tags always | |||
/// point to the same group ID. | |||
/// A group compound can generate several permutation of tags, so that the order of the tag doesn't matter, | |||
/// but for this to work, each permutation must be identified by the same ID (generated by the unique combination) | |||
/// This mechanism is not for thread-safety but to be sure that all the permutations of group tags always | |||
/// point to the same group ID. | |||
/// A group compound can generate several permutation of tags, so that the order of the tag doesn't matter, | |||
/// but for this to work, each permutation must be identified by the same ID (generated by the unique combination) | |||
/// </summary> | |||
static class GroupCompoundInitializer | |||
{ | |||
@@ -23,34 +23,26 @@ namespace Svelto.ECS | |||
where G3 : GroupTag<G3> | |||
where G4 : GroupTag<G4> | |||
{ | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups => | |||
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups); | |||
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); | |||
static int isInitializing; | |||
static GroupCompound() | |||
{ | |||
//avoid race conditions if compounds are using on multiple thread | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 | |||
&& GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) | |||
{ | |||
_Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
var group = new ExclusiveGroup(); | |||
_Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
_Groups.Add(group); | |||
var name = | |||
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint) group}"; | |||
#if DEBUG | |||
GroupNamesMap.idToName[(uint) group] = name; | |||
var name = | |||
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint) group.id}"; | |||
GroupNamesMap.idToName[group] = name; | |||
#endif | |||
GroupHashMap.RegisterGroup(group, name); | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
GroupHashMap.RegisterGroup(group, typeof(GroupCompound<G1, G2, G3, G4>).FullName); | |||
_GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _)); | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = true; | |||
@@ -80,7 +72,7 @@ namespace Svelto.ECS | |||
GroupCompound<G4, G2, G3, G1>._Groups = _Groups; | |||
GroupCompound<G4, G3, G1, G2>._Groups = _Groups; | |||
GroupCompound<G4, G3, G2, G1>._Groups = _Groups; | |||
//all the permutations are warmed up now | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = false; | |||
@@ -107,7 +99,7 @@ namespace Svelto.ECS | |||
GroupCompound<G4, G2, G3, G1>._GroupsHashSet = _GroupsHashSet; | |||
GroupCompound<G4, G3, G1, G2>._GroupsHashSet = _GroupsHashSet; | |||
GroupCompound<G4, G3, G2, G1>._GroupsHashSet = _GroupsHashSet; | |||
GroupCompound<G1, G2, G3>.Add(group); | |||
GroupCompound<G1, G2, G4>.Add(group); | |||
GroupCompound<G1, G3, G4>.Add(group); | |||
@@ -129,66 +121,56 @@ namespace Svelto.ECS | |||
} | |||
} | |||
internal static void Add(ExclusiveGroupStruct @group) | |||
{ | |||
for (int i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("temporary must be transformed in unit test"); | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
public static bool Includes(ExclusiveGroupStruct @group) | |||
{ | |||
return _GroupsHashSet.Contains(@group); | |||
} | |||
} | |||
public abstract class GroupCompound<G1, G2, G3> | |||
where G1 : GroupTag<G1> where G2 : GroupTag<G2> where G3 : GroupTag<G3> | |||
{ | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups => | |||
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups); | |||
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); | |||
static int isInitializing; | |||
public static bool Includes(ExclusiveGroupStruct group) | |||
{ | |||
return _GroupsHashSet.Contains(group); | |||
} | |||
internal static void Add(ExclusiveGroupStruct group) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
for (var i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("temporary must be transformed in unit test"); | |||
throw new Exception("this test must be transformed in unit test"); | |||
#endif | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
public static bool Includes(ExclusiveGroupStruct @group) | |||
{ | |||
return _GroupsHashSet.Contains(@group); | |||
} | |||
//we are changing this with Interlocked, so it cannot be readonly | |||
static int isInitializing; | |||
} | |||
public abstract class GroupCompound<G1, G2, G3> | |||
where G1 : GroupTag<G1> where G2 : GroupTag<G2> where G3 : GroupTag<G3> | |||
{ | |||
static GroupCompound() | |||
{ | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 | |||
&& GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) | |||
{ | |||
_Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
var group = new ExclusiveGroup(); | |||
_Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
_Groups.Add(group); | |||
var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint) group}"; | |||
#if DEBUG | |||
GroupNamesMap.idToName[(uint) group] = name; | |||
var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint) group.id}"; | |||
GroupNamesMap.idToName[group] = name; | |||
#endif | |||
GroupHashMap.RegisterGroup(group, name); | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
GroupHashMap.RegisterGroup(group, typeof(GroupCompound<G1, G2, G3>).FullName); | |||
_GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _)); | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value = true; | |||
@@ -208,7 +190,7 @@ namespace Svelto.ECS | |||
GroupCompound<G3, G2, G1>._GroupsHashSet = _GroupsHashSet; | |||
GroupCompound<G1, G3, G2>._GroupsHashSet = _GroupsHashSet; | |||
GroupCompound<G2, G1, G3>._GroupsHashSet = _GroupsHashSet; | |||
GroupCompound<G1, G2>.Add(group); //<G1/G2> and <G2/G1> must share the same array | |||
GroupCompound<G1, G3>.Add(group); | |||
GroupCompound<G2, G3>.Add(group); | |||
@@ -220,122 +202,157 @@ namespace Svelto.ECS | |||
GroupTag<G3>.Add(group); | |||
} | |||
} | |||
} | |||
public abstract class GroupCompound<G1, G2> where G1 : GroupTag<G1> where G2 : GroupTag<G2> | |||
{ | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups => | |||
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups); | |||
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); | |||
static int isInitializing; | |||
public static bool Includes(ExclusiveGroupStruct group) | |||
{ | |||
return _GroupsHashSet.Contains(group); | |||
} | |||
internal static void Add(ExclusiveGroupStruct group) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
for (var i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("temporary must be transformed in unit test"); | |||
throw new Exception("this test must be transformed in unit test"); | |||
#endif | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
public static bool Includes(ExclusiveGroupStruct @group) | |||
{ | |||
return _GroupsHashSet.Contains(@group); | |||
} | |||
//we are changing this with Interlocked, so it cannot be readonly | |||
static int isInitializing; | |||
} | |||
public abstract class GroupCompound<G1, G2> where G1 : GroupTag<G1> where G2 : GroupTag<G2> | |||
{ | |||
static GroupCompound() | |||
{ | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 | |||
&& GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) | |||
{ | |||
var group = new ExclusiveGroup(); | |||
var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
_Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
_Groups.Add(group); | |||
var groupName = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {(uint) group}"; | |||
#if DEBUG | |||
GroupNamesMap.idToName[(uint) group] = groupName; | |||
GroupNamesMap.idToName[group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {group.id}"; | |||
#endif | |||
GroupHashMap.RegisterGroup(group, groupName); | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
GroupHashMap.RegisterGroup(group, typeof(GroupCompound<G1, G2>).FullName); | |||
_GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _)); | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = true; | |||
GroupCompound<G2, G1>._Groups = _Groups; | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = false; | |||
GroupCompound<G2, G1>._GroupsHashSet = _GroupsHashSet; | |||
//every abstract group preemptively adds this group, it may or may not be empty in future | |||
GroupTag<G1>.Add(group); | |||
GroupTag<G2>.Add(group); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
///A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of | |||
///combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities | |||
///can use the same adjective together with other ones. However since I need to be able to iterate over all the | |||
///groups with the same adjective, a group tag needs to hold all the groups sharing it. | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public abstract class GroupTag<T> where T : GroupTag<T> | |||
{ | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups => | |||
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups); | |||
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); | |||
public static bool Includes(ExclusiveGroupStruct group) | |||
{ | |||
return _GroupsHashSet.Contains(group); | |||
} | |||
internal static void Add(ExclusiveGroupStruct group) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
for (var i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("this test must be transformed in unit test"); | |||
#endif | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
static int isInitializing; | |||
} | |||
/// <summary> | |||
/// A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of | |||
/// combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities | |||
/// can use the same adjective together with other ones. However since I need to be able to iterate over all the | |||
/// groups with the same adjective, a group tag needs to hold all the groups sharing it. | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public abstract class GroupTag<T> where T : GroupTag<T> | |||
{ | |||
static GroupTag() | |||
{ | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0) | |||
{ | |||
var group = new ExclusiveGroup(); | |||
_Groups.Add(group); | |||
#if DEBUG | |||
var typeInfo = typeof(T); | |||
var name = $"Compound: {typeInfo.Name} ID {(uint) group.id}"; | |||
var name = $"Compound: {typeInfo.Name} ID {(uint) @group}"; | |||
#if DEBUG | |||
var typeInfoBaseType = typeInfo.BaseType; | |||
if (typeInfoBaseType.GenericTypeArguments[0] != typeInfo) | |||
if (typeInfoBaseType.GenericTypeArguments[0] != typeInfo) //todo: this should shield from using a pattern different than public class GROUP_NAME : GroupTag<GROUP_NAME> {} however I am not sure it's working | |||
throw new ECSException("Invalid Group Tag declared"); | |||
GroupNamesMap.idToName[(uint) group] = name; | |||
GroupNamesMap.idToName[group] = name; | |||
#endif | |||
GroupHashMap.RegisterGroup(group, name); | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
GroupHashMap.RegisterGroup(group, typeof(GroupTag<T>).FullName); | |||
_GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _)); | |||
} | |||
} | |||
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups => | |||
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups); | |||
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); | |||
public static bool Includes(ExclusiveGroupStruct group) | |||
{ | |||
return _GroupsHashSet.Contains(group); | |||
} | |||
//Each time a new combination of group tags is found a new group is added. | |||
internal static void Add(ExclusiveGroupStruct group) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
for (var i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("temporary must be transformed in unit test"); | |||
throw new Exception("this test must be transformed in unit test"); | |||
#endif | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
public static bool Includes(ExclusiveGroupStruct @group) | |||
{ | |||
return _GroupsHashSet.Contains(@group); | |||
} | |||
//we are changing this with Interlocked, so it cannot be readonly | |||
static int isInitializing; | |||
} | |||
} |
@@ -14,8 +14,10 @@ namespace Svelto.ECS | |||
static NamedExclusiveGroup() | |||
{ | |||
#if DEBUG | |||
GroupNamesMap.idToName[(uint) Group] = $"{name} ID {(uint)Group}"; | |||
GroupNamesMap.idToName[Group] = $"{name} ID {Group.id}"; | |||
#endif | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
GroupHashMap.RegisterGroup(Group, $"{name}"); | |||
} | |||
// protected NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) {} | |||
@@ -28,7 +28,8 @@ namespace Svelto.ECS.Internal | |||
void ExecuteEnginesRemoveCallbacks(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> entityComponentEnginesDB, | |||
in PlatformProfiler profiler, ExclusiveGroupStruct @group); | |||
void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId, EnginesRoot enginesRoot); | |||
void AddEntitiesFromDictionary | |||
(ITypeSafeDictionary entitiesToSubmit, ExclusiveGroupStruct groupId, EnginesRoot enginesRoot); | |||
void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); | |||
void RemoveEntityFromDictionary(EGID fromEntityGid); | |||
@@ -8,29 +8,24 @@ namespace Svelto.ECS.Internal | |||
{ | |||
sealed class TypeSafeDictionary<TValue> : ITypeSafeDictionary<TValue> where TValue : struct, IEntityComponent | |||
{ | |||
static readonly Type _type = typeof(TValue); | |||
static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); | |||
static readonly bool _hasReference = typeof(INeedEntityReference).IsAssignableFrom(_type); | |||
static readonly Type _type = typeof(TValue); | |||
static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); | |||
static readonly bool _hasReference = typeof(INeedEntityReference).IsAssignableFrom(_type); | |||
internal static readonly bool isUnmanaged = | |||
_type.IsUnmanagedEx() && (typeof(IEntityViewComponent).IsAssignableFrom(_type) == false); | |||
internal SveltoDictionary<uint, TValue, ManagedStrategy<SveltoDictionaryNode<uint>>, ManagedStrategy<TValue>, | |||
ManagedStrategy<int>> implMgd; | |||
//used directly by native methods | |||
internal SveltoDictionary<uint, TValue, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<TValue>, | |||
NativeStrategy<int>> implUnmgd; | |||
public TypeSafeDictionary(uint size) | |||
{ | |||
if (isUnmanaged) | |||
implUnmgd = new SveltoDictionary<uint, TValue, NativeStrategy<SveltoDictionaryNode<uint>>, | |||
NativeStrategy<TValue>, NativeStrategy<int>>(size, Allocator.Persistent); | |||
implUnmgd = | |||
new SveltoDictionary<uint, TValue, NativeStrategy<SveltoDictionaryNode<uint>>, | |||
NativeStrategy<TValue>, NativeStrategy<int>>(size, Allocator.Persistent); | |||
else | |||
{ | |||
implMgd = new SveltoDictionary<uint, TValue, ManagedStrategy<SveltoDictionaryNode<uint>>, | |||
ManagedStrategy<TValue>, ManagedStrategy<int>>(size, Allocator.Managed); | |||
implMgd = | |||
new SveltoDictionary<uint, TValue, ManagedStrategy<SveltoDictionaryNode<uint>>, | |||
ManagedStrategy<TValue>, ManagedStrategy<int>>(size, Allocator.Managed); | |||
} | |||
} | |||
@@ -42,7 +37,7 @@ namespace Svelto.ECS.Internal | |||
else | |||
implMgd.Add(egidEntityId, entityComponent); | |||
} | |||
/// todo: Is this really needed, cannot I just use AddEntitiesFromDictionary? Needs to be checked | |||
public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) | |||
{ | |||
@@ -88,7 +83,7 @@ namespace Svelto.ECS.Internal | |||
/// <param name="enginesRoot"></param> | |||
/// <exception cref="TypeSafeDictionaryException"></exception> | |||
public void AddEntitiesFromDictionary | |||
(ITypeSafeDictionary entitiesToSubmit, uint groupId, EnginesRoot enginesRoot) | |||
(ITypeSafeDictionary entitiesToSubmit, ExclusiveGroupStruct groupId, EnginesRoot enginesRoot) | |||
{ | |||
var safeDictionary = (entitiesToSubmit as TypeSafeDictionary<TValue>); | |||
if (isUnmanaged) | |||
@@ -100,9 +95,8 @@ namespace Svelto.ECS.Internal | |||
{ | |||
var egid = new EGID(tuple.Key, groupId); | |||
if (_hasEgid) | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing( | |||
ref tuple.Value, egid); | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref tuple.Value, egid); | |||
if (_hasReference) | |||
SetEGIDWithoutBoxing<TValue>.SetRefWithoutBoxing( | |||
ref tuple.Value, enginesRoot.entityLocator.GetEntityReference(egid)); | |||
@@ -112,7 +106,7 @@ namespace Svelto.ECS.Internal | |||
catch (Exception e) | |||
{ | |||
Console.LogException( | |||
e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key)); | |||
e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId.ToName()).FastConcat(", id ").FastConcat(tuple.Key)); | |||
throw; | |||
} | |||
@@ -133,7 +127,7 @@ namespace Svelto.ECS.Internal | |||
catch (Exception e) | |||
{ | |||
Console.LogException( | |||
e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key)); | |||
e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId.ToName()).FastConcat(", id ").FastConcat(tuple.Key)); | |||
throw; | |||
} | |||
@@ -207,7 +201,10 @@ namespace Svelto.ECS.Internal | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ITypeSafeDictionary Create() { return TypeSafeDictionaryFactory<TValue>.Create(1); } | |||
public ITypeSafeDictionary Create() | |||
{ | |||
return TypeSafeDictionaryFactory<TValue>.Create(1); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint GetIndex(uint valueEntityId) | |||
@@ -297,7 +294,7 @@ namespace Svelto.ECS.Internal | |||
var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); | |||
ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) | |||
, previousGroup, in profiler, toEntityID.Value); | |||
, previousGroup, in profiler, toEntityID.Value); | |||
} | |||
//remove | |||
else | |||
@@ -323,7 +320,7 @@ namespace Svelto.ECS.Internal | |||
var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); | |||
ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) | |||
, previousGroup, in profiler, toEntityID.Value); | |||
, previousGroup, in profiler, toEntityID.Value); | |||
} | |||
else | |||
{ | |||
@@ -465,28 +462,14 @@ namespace Svelto.ECS.Internal | |||
} | |||
} | |||
static void ExecuteEnginesRemoveCallbackOnSingleEntity | |||
(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, ref TValue entity | |||
, in PlatformProfiler profiler, EGID egid) | |||
public void Dispose() | |||
{ | |||
if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) | |||
return; | |||
for (var i = 0; i < entityComponentsEngines.count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityComponentsEngines[i].name)) | |||
{ | |||
(entityComponentsEngines[i].engine as IReactOnAddAndRemove<TValue>).Remove(ref entity, egid); | |||
} | |||
} | |||
catch | |||
{ | |||
Console.LogError( | |||
"Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); | |||
if (isUnmanaged) | |||
implUnmgd.Dispose(); | |||
else | |||
implMgd.Dispose(); | |||
throw; | |||
} | |||
GC.SuppressFinalize(this); | |||
} | |||
void ExecuteEnginesAddOrSwapCallbacksOnSingleEntity | |||
@@ -509,8 +492,7 @@ namespace Svelto.ECS.Internal | |||
} | |||
catch | |||
{ | |||
Console.LogError( | |||
"Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); | |||
Console.LogError("Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); | |||
throw; | |||
} | |||
@@ -528,22 +510,41 @@ namespace Svelto.ECS.Internal | |||
} | |||
catch (Exception) | |||
{ | |||
Console.LogError( | |||
"Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); | |||
Console.LogError("Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); | |||
throw; | |||
} | |||
} | |||
} | |||
public void Dispose() | |||
static void ExecuteEnginesRemoveCallbackOnSingleEntity | |||
(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, ref TValue entity | |||
, in PlatformProfiler profiler, EGID egid) | |||
{ | |||
if (isUnmanaged) | |||
implUnmgd.Dispose(); | |||
else | |||
implMgd.Dispose(); | |||
if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) | |||
return; | |||
GC.SuppressFinalize(this); | |||
for (var i = 0; i < entityComponentsEngines.count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityComponentsEngines[i].name)) | |||
{ | |||
(entityComponentsEngines[i].engine as IReactOnAddAndRemove<TValue>).Remove(ref entity, egid); | |||
} | |||
} | |||
catch | |||
{ | |||
Console.LogError("Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); | |||
throw; | |||
} | |||
} | |||
internal SveltoDictionary<uint, TValue, ManagedStrategy<SveltoDictionaryNode<uint>>, ManagedStrategy<TValue>, | |||
ManagedStrategy<int>> implMgd; | |||
//used directly by native methods | |||
internal SveltoDictionary<uint, TValue, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<TValue>, | |||
NativeStrategy<int>> implUnmgd; | |||
} | |||
} |
@@ -377,8 +377,11 @@ namespace Svelto.ECS.DataStructures | |||
} | |||
} | |||
#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST | |||
[global::Unity.Burst.NoAlias] [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#if UNITY_COLLECTIONS || UNITY_JOBS | |||
#if UNITY_BURST | |||
[global::Unity.Burst.NoAlias] | |||
#endif | |||
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#endif | |||
unsafe UnsafeArray* _list; | |||
#if DEBUG && !PROFILE_SVELTO | |||
@@ -39,8 +39,7 @@ namespace Svelto.ECS | |||
{ | |||
set | |||
{ | |||
if (_notifyOnChange == ReactiveType.ReactOnSet || | |||
EqualityComparer<T>.Default.Equals(_value) == false) | |||
if (_notifyOnChange == ReactiveType.ReactOnSet || _comp.Equals(_value, value) == false) | |||
{ | |||
if (_paused == false) | |||
_subscriber(_senderID, value); | |||
@@ -82,11 +81,12 @@ namespace Svelto.ECS | |||
_paused = true; | |||
} | |||
readonly ReactiveType _notifyOnChange; | |||
readonly EntityReference _senderID; | |||
bool _paused; | |||
Action<EntityReference, T> _subscriber; | |||
T _value; | |||
readonly ReactiveType _notifyOnChange; | |||
readonly EntityReference _senderID; | |||
bool _paused; | |||
Action<EntityReference, T> _subscriber; | |||
T _value; | |||
static readonly EqualityComparer<T> _comp = EqualityComparer<T>.Default; | |||
} | |||
public enum ReactiveType | |||
@@ -47,7 +47,7 @@ namespace Svelto.ECS | |||
continue; | |||
_array.collection = new EntityCollection<T1>(typeSafeDictionary.GetValues(out var count), count); | |||
_array.@group = new ExclusiveGroupStruct(group.Key); | |||
_array.@group = group.Key; | |||
return true; | |||
} | |||
@@ -105,7 +105,7 @@ namespace Svelto.ECS | |||
var reference = buffer.Dequeue<EntityReference>(); | |||
var componentCounts = buffer.Dequeue<uint>(); | |||
Check.Require((uint)egid.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); | |||
Check.Require(egid.groupID.isInvalid == false, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); | |||
var componentBuilders = _nativeAddOperations[componentsIndex].components; | |||
#if DEBUG && !PROFILE_SVELTO | |||
@@ -12,16 +12,16 @@ namespace Svelto.ECS.Extensions.Unity | |||
public struct UECSSveltoGroupID : ISharedComponentData | |||
{ | |||
public readonly uint group; | |||
public readonly ExclusiveGroupStruct group; | |||
public UECSSveltoGroupID(ExclusiveGroupStruct exclusiveGroup) | |||
{ | |||
@group = (uint) exclusiveGroup; | |||
@group = exclusiveGroup; | |||
} | |||
public static implicit operator ExclusiveGroupStruct(UECSSveltoGroupID group) | |||
{ | |||
return new ExclusiveGroupStruct(group.@group); | |||
return group.@group; | |||
} | |||
} | |||
@@ -59,7 +59,7 @@ namespace Svelto.ECS | |||
serializationData.data[serializationData.dataPos++] = (byte) ((entityID >> 24) & 0xff); | |||
// Splitting the groupID (uint, 32 bit) into four bytes. | |||
uint groupID = (uint) egid.groupID; | |||
var groupID = egid.groupID.ToIDAndBitmask(); | |||
serializationData.data[serializationData.dataPos++] = (byte) (groupID & 0xff); | |||
serializationData.data[serializationData.dataPos++] = (byte) ((groupID >> 8) & 0xff); | |||
serializationData.data[serializationData.dataPos++] = (byte) ((groupID >> 16) & 0xff); | |||
@@ -30,8 +30,8 @@ | |||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="System.Memory" Version="4.5.2" /> | |||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" /> | |||
<PackageReference Include="System.Memory" Version="4.5.4" /> | |||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" /> | |||
<None Remove="**\*.meta" /> | |||
<ProjectReference Include="..\com.sebaslab.svelto.common\Svelto.Common.csproj" /> <!-- Do not delete. Used for nuget packing --> | |||
</ItemGroup> |
@@ -3,13 +3,13 @@ | |||
"category": "Svelto", | |||
"description": "Svelto ECS C# Lightweight Data Oriented Entity Component System Framework", | |||
"dependencies": { | |||
"com.sebaslab.svelto.common": "3.2.1" | |||
"com.sebaslab.svelto.common": "3.2.2" | |||
}, | |||
"keywords": [ | |||
"svelto" | |||
], | |||
"name": "com.sebaslab.svelto.ecs", | |||
"version": "3.2.2", | |||
"version": "3.2.3", | |||
"type": "library", | |||
"unity": "2019.3" | |||
} |
@@ -1,3 +1,3 @@ | |||
{ | |||
"version": "3.2.2" | |||
"version": "3.2.3" | |||
} |