diff --git a/src/ECS/CommandBuffer/CommandBuffer.cs b/src/ECS/CommandBuffer/CommandBuffer.cs index 7f692c7f..e1ae2406 100644 --- a/src/ECS/CommandBuffer/CommandBuffer.cs +++ b/src/ECS/CommandBuffer/CommandBuffer.cs @@ -192,7 +192,8 @@ public void Playback() } finally { intern.Reset(hasComponentChanges); - playback.entityChanges.Clear(); + playback.entityChangesCount = 0; + playback.entityChangesIndexes.Clear(); if (!intern.reuseBuffer) { ReturnBuffer(); } @@ -239,34 +240,42 @@ private void ExecuteEntityCommands() private void ExecuteTagCommands(Playback playback) { - var entityChanges = playback.entityChanges; - var nodes = playback.store.nodes.AsSpan(); - var commands = intern.tagCommands.AsSpan(0, intern.tagCommandsCount); + var indexes = playback.entityChangesIndexes; + var changes = playback.entityChanges; + var nodes = playback.store.nodes.AsSpan(); + var commands = intern.tagCommands.AsSpan(0, intern.tagCommandsCount); bool exists; foreach (var tagCommand in commands) { var entityId = tagCommand.entityId; #if NET6_0_OR_GREATER - ref var change = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(entityChanges, entityId, out exists); + ref var changeIndex = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(indexes, entityId, out exists); #else - exists = entityChanges.TryGetValue(entityId, out var change); + exists = indexes.TryGetValue(entityId, out var changeIndex); #endif if (!exists) { var archetype = nodes[entityId].archetype; if (archetype == null) { throw EntityNotFound(tagCommand); } - change.componentTypes = archetype.componentTypes; - change.tags = archetype.tags; - change.oldArchetype = archetype; + changeIndex = playback.entityChangesCount++; + if (changes.Length == changeIndex) { + changes = playback.ResizeChanges(); + } + ref var newChange = ref changes[changeIndex]; + newChange.componentTypes = archetype.componentTypes; + newChange.tags = archetype.tags; + newChange.oldArchetype = archetype; + newChange.entityId = entityId; } + ref var change = ref changes[changeIndex]; if (tagCommand.change == TagChange.Add) { change.tags.bitSet.SetBit(tagCommand.tagIndex); } else { change.tags.bitSet.ClearBit(tagCommand.tagIndex); } - MapUtils.Set(entityChanges, entityId, change); + MapUtils.Set(indexes, entityId, changeIndex); } } @@ -277,13 +286,14 @@ private static void SendTagEvents(Playback playback) if (tagsChanged == null) { return; } - foreach (var (id, change) in playback.entityChanges) + var changes = new Span(playback.entityChanges, 0 , playback.entityChangesCount); + foreach (ref var change in changes) { var oldArchetype = change.oldArchetype; if (change.tags.bitSet.Equals(oldArchetype.tags.bitSet)) { continue; } - tagsChanged.Invoke(new TagsChanged(store, id, change.tags, oldArchetype.tags)); + tagsChanged.Invoke(new TagsChanged(store, change.entityId, change.tags, oldArchetype.tags)); } } @@ -370,21 +380,22 @@ private static InvalidOperationException EntityNotFound(TagCommand command) { private static void UpdateEntityArchetypes(Playback playback) { - var store = playback.store; - var nodes = store.nodes.AsSpan(); + var store = playback.store; + var changes = new Span(playback.entityChanges, 0 , playback.entityChangesCount); + var nodes = store.nodes.AsSpan(); - foreach (var (entityId, change) in playback.entityChanges) + foreach (ref var change in changes) { - var oldArchetype = change.oldArchetype; + var oldArchetype = change.oldArchetype; if (oldArchetype.componentTypes.bitSet.Equals(change.componentTypes.bitSet) && oldArchetype.tags. bitSet.Equals(change.tags. bitSet)) { continue; } // case: archetype changed - ref var node = ref nodes[entityId]; + ref var node = ref nodes[change.entityId]; var newArchetype = store.GetArchetype(change.componentTypes, change.tags); node.archetype = newArchetype; - node.compIndex = Archetype.MoveEntityTo(oldArchetype, entityId, node.compIndex, newArchetype); + node.compIndex = Archetype.MoveEntityTo(oldArchetype, change.entityId, node.compIndex, newArchetype); } } diff --git a/src/ECS/CommandBuffer/Commands/ComponentCommands.cs b/src/ECS/CommandBuffer/Commands/ComponentCommands.cs index 8f4dc0a9..3aabb799 100644 --- a/src/ECS/CommandBuffer/Commands/ComponentCommands.cs +++ b/src/ECS/CommandBuffer/Commands/ComponentCommands.cs @@ -47,10 +47,12 @@ internal ComponentCommands(int structIndex) : base(structIndex) { } internal override void UpdateComponentTypes(Playback playback, bool storeOldComponent) { - var index = structIndex; - var commands = componentCommands.AsSpan(0, commandCount); - var entityChanges = playback.entityChanges; - var nodes = playback.store.nodes.AsSpan(); + var index = structIndex; + var commands = componentCommands.AsSpan(0, commandCount); + var indexes = playback.entityChangesIndexes; + var changes = playback.entityChanges; + var nodes = playback.store.nodes.AsSpan(); + bool exists; // --- set new entity component types for Add/Remove commands @@ -61,9 +63,9 @@ internal override void UpdateComponentTypes(Playback playback, bool storeOldComp } var entityId = command.entityId; #if NET6_0_OR_GREATER - ref var change = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(entityChanges, entityId, out exists); + ref var changeIndex = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(indexes, entityId, out exists); #else - exists = entityChanges.TryGetValue(entityId, out var change); + exists = indexes.TryGetValue(entityId, out var changeIndex); #endif ref var node = ref nodes[entityId]; var archetype = node.archetype; @@ -77,16 +79,23 @@ internal override void UpdateComponentTypes(Playback playback, bool storeOldComp } } if (!exists) { - change.componentTypes = archetype.componentTypes; - change.tags = archetype.tags; - change.oldArchetype = archetype; + changeIndex = playback.entityChangesCount++; + if (changes.Length == changeIndex) { + changes = playback.ResizeChanges(); + } + ref var newChange = ref changes[changeIndex]; + newChange.componentTypes = archetype.componentTypes; + newChange.tags = archetype.tags; + newChange.oldArchetype = archetype; + newChange.entityId = entityId; } + ref var change = ref changes[changeIndex]; if (command.change == Remove) { change.componentTypes.bitSet.ClearBit(index); } else { change.componentTypes.bitSet.SetBit (index); } - MapUtils.Set(entityChanges, entityId, change); + MapUtils.Set(indexes, entityId, changeIndex); } } @@ -124,30 +133,31 @@ internal override void SendCommandEvents(Playback playback) var store = playback.store; var added = store.internBase.componentAdded; var removed = store.internBase.componentRemoved; - var entityChanges = playback.entityChanges; + var indexes = playback.entityChangesIndexes; + var changes = playback.entityChanges; Action changed; foreach (ref var command in commands) { var entityId = command.entityId; - var changes = entityChanges[entityId]; - var oldHeap = changes.oldArchetype.heapMap[index]; - ComponentChangedAction change; + ref var change = ref changes[indexes[entityId]]; + var oldHeap = change.oldArchetype.heapMap[index]; + ComponentChangedAction action; if (command.change == Remove) { - change = Remove; + action = Remove; if (oldHeap == null) { continue; } changed = removed; } else { - change = oldHeap == null ? Add : Update; + action = oldHeap == null ? Add : Update; changed = added; } if (changed == null) { continue; } stashValue = command.oldComponent; - changed.Invoke(new ComponentChanged(store, entityId, change, index, this)); + changed.Invoke(new ComponentChanged(store, entityId, action, index, this)); } } } diff --git a/src/ECS/CommandBuffer/EntityStore.cs b/src/ECS/CommandBuffer/EntityStore.cs index 21f2645d..30a41b90 100644 --- a/src/ECS/CommandBuffer/EntityStore.cs +++ b/src/ECS/CommandBuffer/EntityStore.cs @@ -35,7 +35,7 @@ internal void ReturnCommandBuffer(CommandBuffer commandBuffer) internal Playback GetPlayback() { - if (intern.playback.entityChanges == null) { + if (intern.playback == null) { intern.playback = new Playback(this); } return intern.playback; diff --git a/src/ECS/CommandBuffer/Playback.cs b/src/ECS/CommandBuffer/Playback.cs index f6a1ad58..db9311aa 100644 --- a/src/ECS/CommandBuffer/Playback.cs +++ b/src/ECS/CommandBuffer/Playback.cs @@ -1,8 +1,10 @@ // Copyright (c) Ullrich Praetz - https://github.com/friflo. All rights reserved. // See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; +// ReSharper disable UseCollectionExpression // ReSharper disable once CheckNamespace namespace Friflo.Engine.ECS; @@ -12,16 +14,27 @@ internal struct EntityChange internal ComponentTypes componentTypes; // 32 internal Tags tags; // 32 internal Archetype oldArchetype; // 8 + internal int entityId; // 4 } -internal readonly struct Playback +internal class Playback { - // possible opportunity for optimization as Dictionary values are using 64 bytes - internal readonly Dictionary entityChanges; // 8 - internal readonly EntityStore store; // 8 + /// + /// Store indexes into instead the value directly.
+ /// Size of is too big (96 bytes) which degrade performance when rehashing Dictionary. + ///
+ internal readonly Dictionary entityChangesIndexes; // 8 + internal EntityChange[] entityChanges; // 8 + internal int entityChangesCount; // 4 + internal readonly EntityStore store; // 8 internal Playback(EntityStore store) { - this.store = store; - entityChanges = new Dictionary(); + this.store = store; + entityChangesIndexes = new Dictionary(); + entityChanges = Array.Empty(); + } + + public EntityChange[] ResizeChanges() { + return ArrayUtils.Resize(ref entityChanges, Math.Max(8, 2 * entityChanges.Length)); } } diff --git a/src/Tests/ECS/CommandBuffer/Test_CommandBuffer_Events.cs b/src/Tests/ECS/CommandBuffer/Test_CommandBuffer_Events.cs index 9bb7bfa4..656a797d 100644 --- a/src/Tests/ECS/CommandBuffer/Test_CommandBuffer_Events.cs +++ b/src/Tests/ECS/CommandBuffer/Test_CommandBuffer_Events.cs @@ -1,7 +1,5 @@ using Friflo.Engine.ECS; using NUnit.Framework; -using NUnit.Framework.Internal; -using Tests.Examples; using Tests.Utils; using static NUnit.Framework.Assert; @@ -95,7 +93,7 @@ public static void Test_CommandBuffer_Events_Tags() [Test] public static void Test_CommandBuffer_AddRemoveComponent_Perf() { - int repeat = 10; // 1_000_000 - 13.394 sec + int repeat = 10; // 1_000_000 ~ #PC: 10.298 sec var store = new EntityStore(); var entities = new Entity[100]; for (int n = 0; n < entities.Length; n++) {