diff --git a/src/ECS/Batch/EntityStore.cs b/src/ECS/Batch/EntityStore.cs index 4048d7ce..3424a42b 100644 --- a/src/ECS/Batch/EntityStore.cs +++ b/src/ECS/Batch/EntityStore.cs @@ -4,6 +4,7 @@ using static System.Diagnostics.DebuggerBrowsableState; using Browse = System.Diagnostics.DebuggerBrowsableAttribute; +// ReSharper disable SuggestBaseTypeForParameter // ReSharper disable UseNullPropagation // ReSharper disable ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator // ReSharper disable once CheckNamespace @@ -19,6 +20,8 @@ public partial class EntityStoreBase #region EntityBatch [Browse(Never)] internal int PooledEntityBatchCount => internBase.entityBatches.Count; + + private readonly long indexTypesMak = Static.EntitySchema.indexTypes.bitSet.l0; internal EntityBatch GetBatch(int entityId) { @@ -46,6 +49,15 @@ internal void ApplyBatchTo(EntityBatch batch, int entityId) newComponentTypes.Add (batch.componentsAdd); newComponentTypes.Remove(batch.componentsRemove); + // --- stash old component values only if an event handler is set or an indexed component changes + var oldHeapMap = archetype.heapMap; + var indexChanges = ((batch.componentsAdd.bitSet.l0 | batch.componentsRemove.bitSet.l0) & indexTypesMak) != 0; + var sendRemoveEvents = internBase.componentRemoved != null; + var sendAddEvents = internBase.componentAdded != null; + if (sendRemoveEvents || sendAddEvents || indexChanges) { + StashComponentValues(batch, oldHeapMap, compIndex); + } + // --- change archetype var newArchetype = GetArchetype(newComponentTypes, newTags); if (newArchetype != archetype) { @@ -57,7 +69,21 @@ internal void ApplyBatchTo(EntityBatch batch, int entityId) var newHeapMap = newArchetype.heapMap; var components = batch.batchComponents; foreach (var componentType in batch.componentsAdd) { - newHeapMap[componentType.StructIndex].SetBatchComponent(components, compIndex); + var heap = newHeapMap[componentType.StructIndex]; + heap.SetBatchComponent(components, compIndex); + if (componentType.IndexType != null) { + var entity = new Entity((EntityStore)this, entityId, node.revision); + if (oldHeapMap[componentType.StructIndex] == null) { + heap.AddIndex(entity); + } else { + heap.UpdateIndex(entity); + } + } + } + // --- update indexes of removed indexed components + var removedIndexTypes = batch.componentsRemove.bitSet.l0 & indexTypesMak; + if (removedIndexTypes != 0) { + RemoveComponentIndexes(removedIndexTypes, new Entity((EntityStore)this, entityId, node.revision), oldHeapMap); } // ----------- Send events for all batch commands. See: SEND_EVENT notes @@ -69,16 +95,35 @@ internal void ApplyBatchTo(EntityBatch batch, int entityId) } } // --- send component removed event - if (internBase.componentRemoved != null) { - SendComponentRemoved(batch, entityId, archetype, compIndex); + if (sendRemoveEvents) { + SendComponentRemoved(batch, entityId, archetype); } // --- send component added event - if (internBase.componentAdded != null) { - SendComponentAdded (batch, entityId, archetype, compIndex); + if (sendAddEvents) { + SendComponentAdded (batch, entityId, archetype); + } + } + + private static void StashComponentValues(EntityBatch batch, StructHeap[] oldHeapMap, int compIndex) + { + var componentsChanged = batch.componentsAdd; + componentsChanged.Add(batch.componentsRemove); + foreach (var componentType in componentsChanged) { + oldHeapMap[componentType.StructIndex]?.StashComponent(compIndex); + } + } + + private static void RemoveComponentIndexes(long indexTypes, Entity entity, StructHeap[] oldHeapMap) + { + var indexedComponentsRemove = new ComponentTypes(); + indexedComponentsRemove.bitSet.l0 = indexTypes; + foreach (var componentType in indexedComponentsRemove) { + var heap = oldHeapMap[componentType.StructIndex]; + heap?.RemoveIndex(entity); } } - private void SendComponentAdded(EntityBatch batch, int entityId, Archetype archetype, int compIndex) + private void SendComponentAdded(EntityBatch batch, int entityId, Archetype archetype) { var oldHeapMap = archetype.heapMap; var componentAdded = internBase.componentAdded; @@ -91,14 +136,13 @@ private void SendComponentAdded(EntityBatch batch, int entityId, Archetype arche action = ComponentChangedAction.Add; } else { // --- case: archetype contains the component type => archetype remains unchanged - structHeap.StashComponent(compIndex); action = ComponentChangedAction.Update; } componentAdded.Invoke(new ComponentChanged (this, entityId, action, structIndex, structHeap)); } } - private void SendComponentRemoved(EntityBatch batch, int entityId, Archetype archetype, int compIndex) + private void SendComponentRemoved(EntityBatch batch, int entityId, Archetype archetype) { var oldHeapMap = archetype.heapMap; var componentRemoved = internBase.componentRemoved; @@ -109,7 +153,6 @@ private void SendComponentRemoved(EntityBatch batch, int entityId, Archetype arc if (oldHeap == null) { continue; } - oldHeap.StashComponent(compIndex); componentRemoved.Invoke(new ComponentChanged (this, entityId, ComponentChangedAction.Remove, structIndex, oldHeap)); } } diff --git a/src/Tests/ECS/Arch/Test_Batch.cs b/src/Tests/ECS/Arch/Test_Batch.cs index f1a28101..b16e0ce6 100644 --- a/src/Tests/ECS/Arch/Test_Batch.cs +++ b/src/Tests/ECS/Arch/Test_Batch.cs @@ -114,11 +114,17 @@ public static void Test_Batch_ApplyTo() { var store = new EntityStore(PidType.RandomPids); var batch = new EntityBatch(); - batch.Add (new Position()); + batch.Add (new Position(2,2,2)); batch.AddTag(); - var entity1 = store.CreateEntity(); - var entity2 = store.CreateEntity(); + var entity1 = store.CreateEntity(1); + var entity2 = store.CreateEntity(2); + entity1.AddComponent(new Position(1,1,1)); + store.OnComponentAdded += changed => { + if (changed.Entity.Id == 1) { + AreEqual(new Position(1,1,1), changed.OldComponent()); + } + }; batch.ApplyTo(entity1) .ApplyTo(entity2); AreEqual(0, store.Info.PooledEntityBatchCount); @@ -295,6 +301,25 @@ public static void Test_Batch_EntityBatch_Perf() AreEqual(0, store.Info.PooledEntityBatchCount); } + [Test] + public static void Test_Batch_ApplyTo_Perf() + { + var count = 10; // 50_000_000 ~ #PC: 3278 ms + var store = new EntityStore(); + var batch = new EntityBatch(); + batch.Add(new Position()); + batch.Add(new Rotation()); + batch.Add(new Scale3()); + + var sw = new Stopwatch(); + sw.Start(); + var entity1 = store.CreateEntity(1); + for (int n = 0; n < count; n++) { + batch.ApplyTo(entity1); + } + Console.WriteLine($"Test_Batch_ApplyTo_Perf() - duration: {sw.ElapsedMilliseconds} ms"); + } + [Test] public static void Test_Batch_obsolete() { var store = new EntityStore(PidType.RandomPids); diff --git a/src/Tests/ECS/Serialize/Test_SerializeIndexedComponent.cs b/src/Tests/ECS/Serialize/Test_SerializeIndexedComponent.cs index a1e6eb6a..8b1ccf58 100644 --- a/src/Tests/ECS/Serialize/Test_SerializeIndexedComponent.cs +++ b/src/Tests/ECS/Serialize/Test_SerializeIndexedComponent.cs @@ -133,6 +133,34 @@ public static void Test_IndexedComponent_Remove() AreEqual(0, store.GetEntitiesWithComponentValue(40).Count); AreEqual(0, store.GetAllIndexedComponentValues().Count); } + + [Test] + public static void Test_IndexedComponent_ApplyTo() + { + var store = new EntityStore(PidType.RandomPids); + var batch = new EntityBatch(); + batch.Add (new IndexedInt { value = 50 }); + + var entity1 = store.CreateEntity(1); + batch.ApplyTo(entity1); + AreEqual(1, store.GetEntitiesWithComponentValue(50).Count); + AreEqual(1, store.GetAllIndexedComponentValues().Count); + + batch = new EntityBatch(); + batch.Add (new IndexedInt { value = 51 }); + batch.ApplyTo(entity1); + AreEqual(1, store.GetEntitiesWithComponentValue(51).Count); + AreEqual(0, store.GetEntitiesWithComponentValue(50).Count); + AreEqual(1, store.GetAllIndexedComponentValues().Count); + + batch = new EntityBatch(); + batch.Remove(); + batch.ApplyTo(entity1); + AreEqual(0, store.GetEntitiesWithComponentValue(51).Count); + AreEqual(0, store.GetAllIndexedComponentValues().Count); + + batch.ApplyTo(entity1); + } } } \ No newline at end of file