From 2b8e0434cb73f2774333b42481826eb3922c837e Mon Sep 17 00:00:00 2001 From: Jose Luis Rey Mendez Date: Sun, 9 Aug 2020 20:42:22 -0300 Subject: [PATCH 1/4] Added EntityLocatorMap with entitiesDB API and updates on entity submissions --- Svelto.ECS/EnginesRoot.Engines.cs | 10 +- Svelto.ECS/EnginesRoot.Entities.cs | 15 ++ Svelto.ECS/EnginesRoot.LocatorMap.cs | 200 ++++++++++++++++++++++++++ Svelto.ECS/EntitiesDB.cs | 30 +++- Svelto.ECS/EntityLocator.cs | 56 ++++++++ Svelto.ECS/EntityLocatorMapElement.cs | 16 +++ Svelto.ECS/IEntityLocatorMap.cs | 9 ++ 7 files changed, 325 insertions(+), 11 deletions(-) create mode 100644 Svelto.ECS/EnginesRoot.LocatorMap.cs create mode 100644 Svelto.ECS/EntityLocator.cs create mode 100644 Svelto.ECS/EntityLocatorMapElement.cs create mode 100644 Svelto.ECS/IEntityLocatorMap.cs diff --git a/Svelto.ECS/EnginesRoot.Engines.cs b/Svelto.ECS/EnginesRoot.Engines.cs index 70c8266..a3c3754 100644 --- a/Svelto.ECS/EnginesRoot.Engines.cs +++ b/Svelto.ECS/EnginesRoot.Engines.cs @@ -50,9 +50,11 @@ public EnginesRoot(IEntitiesSubmissionScheduler entitiesComponentScheduler) new FasterDictionary, ITypeSafeDictionary>>(); _groupsPerEntity = new FasterDictionary, FasterDictionary>(); _groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); + _entityLocatorMap = new FasterList(); + _egidToLocatorMap = new FasterDictionary>(); _entitiesStream = new EntitiesStream(); - _entitiesDB = new EntitiesDB(_groupEntityComponentsDB, _groupsPerEntity, _entitiesStream); + _entitiesDB = new EntitiesDB(_groupEntityComponentsDB, _groupsPerEntity, _entitiesStream, new EntityLocatorMap(this)); scheduler = entitiesComponentScheduler; scheduler.onTick = new EntitiesSubmitter(this); @@ -88,7 +90,7 @@ public void Dispose() Svelto.Console.LogException(e); } } - + foreach (FasterDictionary, ITypeSafeDictionary>>. KeyValuePairFast groups in _groupEntityComponentsDB) { @@ -104,7 +106,7 @@ public void Dispose() Svelto.Console.LogException(e); } } - + foreach (FasterDictionary, ITypeSafeDictionary>>. KeyValuePairFast groups in _groupEntityComponentsDB) { @@ -129,7 +131,7 @@ public void Dispose() #endif _groupedEntityToAdd.Dispose(); _entitiesStream.Dispose(); - + scheduler.Dispose(); } diff --git a/Svelto.ECS/EnginesRoot.Entities.cs b/Svelto.ECS/EnginesRoot.Entities.cs index f25e6a3..fe53f07 100644 --- a/Svelto.ECS/EnginesRoot.Entities.cs +++ b/Svelto.ECS/EnginesRoot.Entities.cs @@ -26,6 +26,7 @@ EntityComponentInitializer BuildEntity { CheckAddEntityID(entityID, implementorType); Check.Require(entityID.groupID != 0, "invalid group detected"); + CreateLocator(entityID); var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild , implementors); @@ -70,6 +71,16 @@ void MoveEntityFromAndToEngines(IComponentBuilder[] componentBuilders, EGID from { var fromGroup = GetGroup(fromEntityGID.groupID); + // Update the egid to unique id maps. + if (toEntityGID.HasValue) + { + UpdateLocator(fromEntityGID, toEntityGID.Value); + } + else + { + RemoveLocator(fromEntityGID); + } + //Check if there is an EntityInfoView linked to this entity, if so it's a DynamicEntityDescriptor! if (fromGroup.TryGetValue(new RefWrapper(ComponentBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) , out var entityInfoViewDic) @@ -192,6 +203,8 @@ void SwapEntitiesBetweenGroups(uint fromIdGroupId, uint toGroupId, in PlatformPr FasterDictionary, ITypeSafeDictionary> fromGroup = GetGroup(fromIdGroupId); FasterDictionary, ITypeSafeDictionary> toGroup = GetOrCreateGroup(toGroupId, profiler); + UpdateAllGroupLocators(fromIdGroupId, toGroupId); + foreach (FasterDictionary, ITypeSafeDictionary>.KeyValuePairFast dictionaryOfEntities in fromGroup) { @@ -273,6 +286,8 @@ void RemoveGroupAndEntities(uint groupID, in PlatformProfiler profiler) FasterDictionary, ITypeSafeDictionary> dictionariesOfEntities = _groupEntityComponentsDB[groupID]; + RemoveAllGroupLocators(groupID); + foreach (FasterDictionary, ITypeSafeDictionary>.KeyValuePairFast dictionaryOfEntities in dictionariesOfEntities) { dictionaryOfEntities.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler diff --git a/Svelto.ECS/EnginesRoot.LocatorMap.cs b/Svelto.ECS/EnginesRoot.LocatorMap.cs new file mode 100644 index 0000000..de5d0a1 --- /dev/null +++ b/Svelto.ECS/EnginesRoot.LocatorMap.cs @@ -0,0 +1,200 @@ +using System.Collections.Specialized; +using System.Runtime.CompilerServices; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public partial class EnginesRoot + { + // The EntityLocatorMap provides a bidirectional map to help locate entities without using an EGID which might + // change in runtime. The Entity Locator map uses a reusable unique identifier struct called EntityLocator to + // find the last known EGID from last entity submission. + class EntityLocatorMap : IEntityLocatorMap + { + public EntityLocatorMap(EnginesRoot enginesRoot) + { + _enginesRoot = new WeakReference(enginesRoot); + } + + public EntityLocator GetLocator(EGID egid) + { + return _enginesRoot.Target.GetLocator(egid); + } + + public EGID GetEGID(EntityLocator locator) + { + return _enginesRoot.Target.FindEGID(locator); + } + + WeakReference _enginesRoot; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CreateLocator(EGID egid) + { + // Check if we need to create a new EntityLocator or whether we can recycle an existing one.s + EntityLocator locator; + if (_nextEntityId == _entityLocatorMap.count) + { + _entityLocatorMap.Add(new EntityLocatorMapElement(egid)); + locator = new EntityLocator(_nextEntityId++); + } + else + { + ref var element = ref _entityLocatorMap[_nextEntityId]; + locator = new EntityLocator(_nextEntityId, element.version); + // The recycle entities form a linked list, using the egid.entityID to store the next element. + _nextEntityId = element.egid.entityID; + element.egid = egid; + } + + // When we create a new one there is nothing to recycle anymore, so we need to update the last recycle entityId. + if (_nextEntityId == _entityLocatorMap.count) + { + _lastEntityId = _entityLocatorMap.count; + } + + // Update reverse map from egid to locator. + if (_egidToLocatorMap.TryGetValue(egid.groupID, out var groupMap) == false) + { + groupMap = new FasterDictionary(); + _egidToLocatorMap[egid.groupID] = groupMap; + } + groupMap[egid.entityID] = locator; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void UpdateLocator(EGID from, EGID to) + { + var locator = GetAndRemoveLocator(from); + + _entityLocatorMap[locator.uniqueID].egid = to; + + if (_egidToLocatorMap.TryGetValue(to.groupID, out var toGroupMap)) + { + toGroupMap[to.entityID] = locator; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void RemoveLocator(EGID egid) + { + var locator = GetAndRemoveLocator(egid); + + // Check if this is the first recycled element. + if (_lastEntityId == _entityLocatorMap.count) + { + _nextEntityId = locator.uniqueID; + } + // Otherwise add it as the last recycled element. + else + { + _entityLocatorMap[_lastEntityId].egid = new EGID(locator.uniqueID, 0); + } + + // Invalidate the entity locator element by bumping its version and setting the egid to point to a unexisting element. + _entityLocatorMap[locator.uniqueID].egid = new EGID(_entityLocatorMap.count, 0); + _entityLocatorMap[locator.uniqueID].version++; + + // Mark the element as the last element used. + _lastEntityId = locator.uniqueID; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void RemoveAllGroupLocators(uint groupId) + { + if (_egidToLocatorMap.TryGetValue(groupId, out var groupMap) == false) + { + return; + } + + // We need to traverse all entities in the group and remove the locator using the egid. + // RemoveLocator would modify the enumerator so this is why we traverse the dictionary from last to first. + var keys = groupMap.unsafeKeys; + for (var i = groupMap.count - 1; true; i--) + { + RemoveLocator(new EGID(keys[i].key, groupId)); + if (i == 0) break; + } + + _egidToLocatorMap.Remove(groupId); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void UpdateAllGroupLocators(uint fromGroupId, uint toGroupId) + { + if (_egidToLocatorMap.TryGetValue(fromGroupId, out var groupMap) == false) + { + return; + } + + // We need to traverse all entities in the group and update the locator using the egid. + // UpdateLocator would modify the enumerator so this is why we traverse the dictionary from last to first. + var keys = groupMap.unsafeKeys; + for (var i = groupMap.count - 1; true; i--) + { + UpdateLocator(new EGID(keys[i].key, fromGroupId), new EGID(keys[i].key, toGroupId)); + if (i == 0) break; + } + + _egidToLocatorMap.Remove(fromGroupId); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + EntityLocator GetLocator(EGID egid) + { + if (_egidToLocatorMap.TryGetValue(egid.groupID, out var groupMap)) + { + if (groupMap.TryGetValue(egid.entityID, out var locator)) + { + return locator; + } + } + + return EntityLocator.Invalid; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + EntityLocator GetAndRemoveLocator(EGID egid) + { + if (_egidToLocatorMap.TryGetValue(egid.groupID, out var groupMap)) + { + if (groupMap.TryGetValue(egid.entityID, out var locator)) + { + groupMap.Remove(egid.entityID); + return locator; + } + } + + return EntityLocator.Invalid; + } + + EGID FindEGID(EntityLocator locator) + { +#if DEBUG && !PROFILE_SVELTO + if (locator.uniqueID >= _entityLocatorMap.count) + { + throw new ECSException("EntityLocator is out of bounds."); + } +#endif + // Make sure we are querying for the current version of the locator. + // Otherwise the locator is pointing to a removed entity. + if (_entityLocatorMap[locator.uniqueID].version == locator.version) + { + return _entityLocatorMap[locator.uniqueID].egid; + } + else + { +#if DEBUG && !PROFILE_SVELTO + throw new ECSException("Attempting to find EGID with outdated entityLocator"); +#endif + return new EGID(); + } + } + + uint _nextEntityId; + uint _lastEntityId; + readonly FasterList _entityLocatorMap; + readonly FasterDictionary> _egidToLocatorMap; + } +} \ No newline at end of file diff --git a/Svelto.ECS/EntitiesDB.cs b/Svelto.ECS/EntitiesDB.cs index 781f4dd..00f83ba 100644 --- a/Svelto.ECS/EntitiesDB.cs +++ b/Svelto.ECS/EntitiesDB.cs @@ -16,11 +16,25 @@ public partial class EntitiesDB internal EntitiesDB (FasterDictionary, ITypeSafeDictionary>> groupEntityComponentsDB , FasterDictionary, FasterDictionary> groupsPerEntity - , EntitiesStream entityStream) + , EntitiesStream entityStream + , IEntityLocatorMap entityLocatorMap) { _groupEntityComponentsDB = groupEntityComponentsDB; _groupsPerEntity = groupsPerEntity; _entityStream = entityStream; + _entityLocatorMap = entityLocatorMap; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EGID GetEGID(EntityLocator entityLocator) + { + return _entityLocatorMap.GetEGID(entityLocator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityLocator GetLocator(EGID egid) + { + return _entityLocatorMap.GetLocator(egid); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -75,7 +89,7 @@ public EntityCollection QueryEntities(ExclusiveGroupStruct group .FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count) .FastConcat(" ", typeof(T2).ToString()) .FastConcat("' group: ", groupStruct.ToName()))); -#endif +#endif return new EntityCollection(T1entities, T2entities); } @@ -96,16 +110,16 @@ public EntityCollection QueryEntities(ExclusiveGroupStru .FastConcat(T2entities.count) .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) .FastConcat(" count: ").FastConcat(T3entities.count))); -#endif +#endif return new EntityCollection(T1entities, T2entities, T3entities); } - + public int IterateOverGroupsAndCount (in LocalFasterReadOnlyList groups) where T : struct, IEntityComponent { int count = 0; - + for (int i = 0; i < groups.count; i++) { count += Count(groups[i]); @@ -154,7 +168,7 @@ public EGIDMapper QueryMappedEntities(ExclusiveGroupStruct groupStructId) return (typeSafeDictionary as ITypeSafeDictionary).ToEGIDMapper(groupStructId); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryQueryMappedEntities (ExclusiveGroupStruct groupStructId, out EGIDMapper mapper) where T : struct, IEntityComponent @@ -210,7 +224,7 @@ public int Count(ExclusiveGroupStruct groupStruct) where T : struct, IEntityC { if (SafeQueryEntityDictionary(groupStruct, out var typeSafeDictionary) == false) return 0; - + return (int) typeSafeDictionary.count; } @@ -324,5 +338,7 @@ readonly FasterDictionary, ITypeSafeDict //needed to be able to track in which groups a specific entity type can be found. //may change in future as it could be expanded to support queries readonly FasterDictionary, FasterDictionary> _groupsPerEntity; + + IEntityLocatorMap _entityLocatorMap; } } \ No newline at end of file diff --git a/Svelto.ECS/EntityLocator.cs b/Svelto.ECS/EntityLocator.cs new file mode 100644 index 0000000..a1946c9 --- /dev/null +++ b/Svelto.ECS/EntityLocator.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.InteropServices; + +#pragma warning disable 660,661 + +namespace Svelto.ECS +{ + [Serialization.DoNotSerialize] + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct EntityLocator : IEquatable + { + [FieldOffset(0)] public readonly uint uniqueID; + [FieldOffset(4)] public readonly uint version; + [FieldOffset(0)] readonly ulong _GID; + + public static bool operator ==(EntityLocator obj1, EntityLocator obj2) + { + return obj1._GID == obj2._GID; + } + + public static bool operator !=(EntityLocator obj1, EntityLocator obj2) + { + return obj1._GID != obj2._GID; + } + + public EntityLocator(uint uniqueId) : this(uniqueId, 0) {} + + public EntityLocator(uint uniqueId, uint version) : this() + { + _GID = MAKE_GLOBAL_ID(uniqueId, version); + } + + static ulong MAKE_GLOBAL_ID(uint uniqueId, uint version) + { + return (ulong)version << 32 | ((ulong)uniqueId & 0xFFFFFFFF); + } + + public static EntityLocator Invalid => new EntityLocator(uint.MaxValue, uint.MaxValue); + + public bool Equals(EntityLocator other) + { + return _GID == other._GID; + } + + public bool Equals(EntityLocator x, EntityLocator y) + { + return x == y; + } + + public override string ToString() + { + return "id:".FastConcat(uniqueID).FastConcat(" version:").FastConcat(version); + } + } +} diff --git a/Svelto.ECS/EntityLocatorMapElement.cs b/Svelto.ECS/EntityLocatorMapElement.cs new file mode 100644 index 0000000..16581f5 --- /dev/null +++ b/Svelto.ECS/EntityLocatorMapElement.cs @@ -0,0 +1,16 @@ +using System; + +namespace Svelto.ECS +{ + internal struct EntityLocatorMapElement + { + internal EGID egid; + internal uint version; + + internal EntityLocatorMapElement(EGID egid) + { + this.egid = egid; + version = 0; + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/IEntityLocatorMap.cs b/Svelto.ECS/IEntityLocatorMap.cs new file mode 100644 index 0000000..d32e389 --- /dev/null +++ b/Svelto.ECS/IEntityLocatorMap.cs @@ -0,0 +1,9 @@ +namespace Svelto.ECS +{ + public interface IEntityLocatorMap + { + EntityLocator GetLocator(EGID egid); + + EGID GetEGID(EntityLocator locator); + } +} \ No newline at end of file From e723312ea32694154f23043e04383fe4e952a9c5 Mon Sep 17 00:00:00 2001 From: Jose Luis Rey Mendez Date: Sun, 9 Aug 2020 20:43:08 -0300 Subject: [PATCH 2/4] Make reference to entity locator map readonly in entity db --- Svelto.ECS/EntitiesDB.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Svelto.ECS/EntitiesDB.cs b/Svelto.ECS/EntitiesDB.cs index 00f83ba..34725e6 100644 --- a/Svelto.ECS/EntitiesDB.cs +++ b/Svelto.ECS/EntitiesDB.cs @@ -339,6 +339,6 @@ readonly FasterDictionary, ITypeSafeDict //may change in future as it could be expanded to support queries readonly FasterDictionary, FasterDictionary> _groupsPerEntity; - IEntityLocatorMap _entityLocatorMap; + readonly IEntityLocatorMap _entityLocatorMap; } } \ No newline at end of file From 037844f2a86f05d74d0d9eedf537448a071ae3ef Mon Sep 17 00:00:00 2001 From: Jose Luis Rey Mendez Date: Mon, 10 Aug 2020 12:10:56 -0300 Subject: [PATCH 3/4] Fixed update locator would fail to create dictionaries for new groups --- Svelto.ECS/EnginesRoot.LocatorMap.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Svelto.ECS/EnginesRoot.LocatorMap.cs b/Svelto.ECS/EnginesRoot.LocatorMap.cs index de5d0a1..58a81d3 100644 --- a/Svelto.ECS/EnginesRoot.LocatorMap.cs +++ b/Svelto.ECS/EnginesRoot.LocatorMap.cs @@ -68,12 +68,24 @@ void UpdateLocator(EGID from, EGID to) { var locator = GetAndRemoveLocator(from); +#if DEBUG && !PROFILE_SVELTO + if (locator.Equals(EntityLocator.Invalid)) + { + throw new ECSException("Unable to update locator from egid: " + .FastConcat(from.ToString(), "to egid: ") + .FastConcat(to.ToString(), ". Locator was not found") + ); + } +#endif + _entityLocatorMap[locator.uniqueID].egid = to; - if (_egidToLocatorMap.TryGetValue(to.groupID, out var toGroupMap)) + if (_egidToLocatorMap.TryGetValue(to.groupID, out var groupMap) == false) { - toGroupMap[to.entityID] = locator; + groupMap = new FasterDictionary(); + _egidToLocatorMap[to.groupID] = groupMap; } + groupMap[to.entityID] = locator; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -81,6 +93,15 @@ void RemoveLocator(EGID egid) { var locator = GetAndRemoveLocator(egid); +#if DEBUG && !PROFILE_SVELTO + if (locator.Equals(EntityLocator.Invalid)) + { + throw new ECSException("Unable to remove locator for egid: " + .FastConcat(egid.ToString(), ". Locator was not found") + ); + } +#endif + // Check if this is the first recycled element. if (_lastEntityId == _entityLocatorMap.count) { From f172620d23ad0fcbdd5c2df8ad582626fa4dd205 Mon Sep 17 00:00:00 2001 From: Jose Luis Rey Mendez Date: Wed, 12 Aug 2020 16:37:32 -0300 Subject: [PATCH 4/4] Changed GetEGID api to TryGetEGID returning a bool value --- Svelto.ECS/EnginesRoot.LocatorMap.cs | 22 ++++++++-------------- Svelto.ECS/EntitiesDB.cs | 4 ++-- Svelto.ECS/IEntityLocatorMap.cs | 2 +- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Svelto.ECS/EnginesRoot.LocatorMap.cs b/Svelto.ECS/EnginesRoot.LocatorMap.cs index 58a81d3..4f2b7a5 100644 --- a/Svelto.ECS/EnginesRoot.LocatorMap.cs +++ b/Svelto.ECS/EnginesRoot.LocatorMap.cs @@ -21,9 +21,9 @@ public EntityLocator GetLocator(EGID egid) return _enginesRoot.Target.GetLocator(egid); } - public EGID GetEGID(EntityLocator locator) + public bool TryGetEGID(EntityLocator locator, out EGID egid) { - return _enginesRoot.Target.FindEGID(locator); + return _enginesRoot.Target.TryGetEGID(locator, out egid); } WeakReference _enginesRoot; @@ -190,26 +190,20 @@ EntityLocator GetAndRemoveLocator(EGID egid) return EntityLocator.Invalid; } - EGID FindEGID(EntityLocator locator) + bool TryGetEGID(EntityLocator locator, out EGID egid) { -#if DEBUG && !PROFILE_SVELTO - if (locator.uniqueID >= _entityLocatorMap.count) - { - throw new ECSException("EntityLocator is out of bounds."); - } -#endif + egid = new EGID(); + if (locator == EntityLocator.Invalid) return false; // Make sure we are querying for the current version of the locator. // Otherwise the locator is pointing to a removed entity. if (_entityLocatorMap[locator.uniqueID].version == locator.version) { - return _entityLocatorMap[locator.uniqueID].egid; + egid = _entityLocatorMap[locator.uniqueID].egid; + return true; } else { -#if DEBUG && !PROFILE_SVELTO - throw new ECSException("Attempting to find EGID with outdated entityLocator"); -#endif - return new EGID(); + return false; } } diff --git a/Svelto.ECS/EntitiesDB.cs b/Svelto.ECS/EntitiesDB.cs index 34725e6..24da167 100644 --- a/Svelto.ECS/EntitiesDB.cs +++ b/Svelto.ECS/EntitiesDB.cs @@ -26,9 +26,9 @@ internal EntitiesDB } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EGID GetEGID(EntityLocator entityLocator) + public bool FindEGID(EntityLocator entityLocator, out EGID egid) { - return _entityLocatorMap.GetEGID(entityLocator); + return _entityLocatorMap.TryGetEGID(entityLocator, out egid); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Svelto.ECS/IEntityLocatorMap.cs b/Svelto.ECS/IEntityLocatorMap.cs index d32e389..91687f2 100644 --- a/Svelto.ECS/IEntityLocatorMap.cs +++ b/Svelto.ECS/IEntityLocatorMap.cs @@ -4,6 +4,6 @@ public interface IEntityLocatorMap { EntityLocator GetLocator(EGID egid); - EGID GetEGID(EntityLocator locator); + bool TryGetEGID(EntityLocator locator, out EGID egid); } } \ No newline at end of file