From 0883053f3ca60ee35030f48ad2ac8c4ae5f605ae Mon Sep 17 00:00:00 2001 From: Ullrich Praetz Date: Sat, 17 Aug 2024 09:56:47 +0200 Subject: [PATCH] ECS - optimize: add TypeCache for Entity.AddComponent<>() / RemoveComponent<>() --- src/ECS/Archetype/Archetype.cs | 2 + src/ECS/Archetype/EntityStore.Archetype.cs | 16 ++++++-- src/ECS/Archetype/TypeCache.cs | 42 ++++++++++++++++++++ src/Tests-internal/ECS/Test_sizeof.cs | 7 ++++ src/Tests/ECS/Entity/Test_StructComponent.cs | 37 +++++++++++++++++ 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/ECS/Archetype/TypeCache.cs diff --git a/src/ECS/Archetype/Archetype.cs b/src/ECS/Archetype/Archetype.cs index d9357815e..18187918d 100644 --- a/src/ECS/Archetype/Archetype.cs +++ b/src/ECS/Archetype/Archetype.cs @@ -91,6 +91,8 @@ public Span Components() where TComponen [Browse(Never)] internal readonly int archIndex; // 4 - archetype index in EntityStore.archs[] [Browse(Never)] internal readonly StandardComponents std; // 32 - heap references to std types: Position, Rotation, ... [Browse(Never)] private ArchetypeQuery query; // 8 - return the entities of this archetype + [Browse(Never)] internal TypeCache componentAdd; // 24 + [Browse(Never)] internal TypeCache componentRemove;// 24 #endregion #region public methods diff --git a/src/ECS/Archetype/EntityStore.Archetype.cs b/src/ECS/Archetype/EntityStore.Archetype.cs index 16b0f4c59..67fa6e4ca 100644 --- a/src/ECS/Archetype/EntityStore.Archetype.cs +++ b/src/ECS/Archetype/EntityStore.Archetype.cs @@ -76,32 +76,40 @@ internal bool TryGetValue(ArchetypeKey searchKey, out ArchetypeKey archetypeKey) internal static Archetype GetArchetypeWith(EntityStoreBase store, Archetype current, int structIndex) { + var typeIndex = current.componentAdd.FindType(structIndex); + if (typeIndex >= 0) { + return current.store.archs[typeIndex]; + } var key = store.searchKey; key.SetWith(current, structIndex); if (store.archSet.TryGetValue(key, out var archetypeKey)) { - return archetypeKey.archetype; + return current.componentAdd.Cache((byte)structIndex, archetypeKey.archetype); } var config = GetArchetypeConfig(store); var componentTypes = current.componentTypes; componentTypes.bitSet.SetBit(structIndex); var archetype = Archetype.CreateWithComponentTypes(config, componentTypes, current.tags); AddArchetype(store, archetype); - return archetype; + return current.componentAdd.Cache((byte)structIndex, archetype); } internal static Archetype GetArchetypeWithout(EntityStoreBase store, Archetype archetype, int structIndex) { + var typeIndex = archetype.componentRemove.FindType(structIndex); + if (typeIndex >= 0) { + return archetype.store.archs[typeIndex]; + } var key = store.searchKey; key.SetWithout(archetype, structIndex); if (store.archSet.TryGetValue(key, out var archetypeKey)) { - return archetypeKey.archetype; + return archetype.componentRemove.Cache((byte)structIndex, archetypeKey.archetype); } var config = GetArchetypeConfig(store); var componentTypes = archetype.componentTypes; componentTypes.bitSet.ClearBit(structIndex); var result = Archetype.CreateWithComponentTypes(config, componentTypes, archetype.tags); AddArchetype(store, result); - return result; + return archetype.componentRemove.Cache((byte)structIndex, result); } private static Archetype GetArchetypeWithTags(EntityStoreBase store, Archetype archetype, in Tags tags) diff --git a/src/ECS/Archetype/TypeCache.cs b/src/ECS/Archetype/TypeCache.cs new file mode 100644 index 000000000..9f11c5d7c --- /dev/null +++ b/src/ECS/Archetype/TypeCache.cs @@ -0,0 +1,42 @@ +// Copyright (c) Ullrich Praetz - https://github.com/friflo. All rights reserved. +// See LICENSE file in the project root for full license information. + +// ReSharper disable once CheckNamespace +namespace Friflo.Engine.ECS; + +internal struct TypeCache +{ + private byte type0; // 1 + private byte type1; // 1 + private byte type2; // 1 + private byte type3; // 1 + + private int index0; // 4 + private int index1; // 4 + private int index2; // 4 + private int index3; // 4 + + private int next; // 4 + + internal int FindType(int type) + { + if (type == type0) return index0; + if (type == type1) return index1; + if (type == type2) return index2; + if (type == type3) return index3; + return -1; + } + + internal Archetype Cache(byte type, Archetype archetype) + { + var index = archetype.archIndex; + var at = next++ % 4; + switch (at) { + case 0: type0 = type; index0 = index; break; + case 1: type1 = type; index1 = index; break; + case 2: type2 = type; index2 = index; break; + case 3: type3 = type; index3 = index; break; + } + return archetype; + } +} \ No newline at end of file diff --git a/src/Tests-internal/ECS/Test_sizeof.cs b/src/Tests-internal/ECS/Test_sizeof.cs index 39e60767f..50345c90a 100644 --- a/src/Tests-internal/ECS/Test_sizeof.cs +++ b/src/Tests-internal/ECS/Test_sizeof.cs @@ -72,6 +72,13 @@ public static void Test_sizeof_StructIndexes() { AreEqual(16, size); } + [Test] + public static void Test_sizeof_TypeCache() { + var type = typeof(TypeCache); + var size = Marshal.SizeOf(type!); + AreEqual(24, size); + } + [Test] public static unsafe void Test_Math_sizeof() { var size = sizeof(Position); diff --git a/src/Tests/ECS/Entity/Test_StructComponent.cs b/src/Tests/ECS/Entity/Test_StructComponent.cs index b42eaf656..327901edc 100644 --- a/src/Tests/ECS/Entity/Test_StructComponent.cs +++ b/src/Tests/ECS/Entity/Test_StructComponent.cs @@ -516,6 +516,43 @@ public static void Test_StructComponent_Archetype_CreateEntity_default_component AreEqual(0, entity.MyComponent1().a); } + [Test] + public static void Test_StructComponent_Cache() + { + var store = new EntityStore(); + var entity = store.CreateEntity(); + + IsTrue (entity.AddComponent(new MyComponent1())); + IsTrue (entity.RemoveComponent()); + IsTrue (entity.AddComponent(new MyComponent1 { a = 10 })); + AreEqual(10, entity.GetComponent().a); + IsTrue (entity.RemoveComponent()); + + IsTrue (entity.AddComponent(new MyComponent2())); + IsTrue (entity.RemoveComponent()); + IsTrue (entity.AddComponent(new MyComponent2 { b = 11 })); + AreEqual(11, entity.GetComponent().b); + IsTrue (entity.RemoveComponent()); + + IsTrue (entity.AddComponent(new MyComponent3())); + IsTrue (entity.RemoveComponent()); + IsTrue (entity.AddComponent(new MyComponent3 { b = 12 })); + AreEqual(12, entity.GetComponent().b); + IsTrue (entity.RemoveComponent()); + + IsTrue (entity.AddComponent(new MyComponent4())); + IsTrue (entity.RemoveComponent()); + IsTrue (entity.AddComponent(new MyComponent4 { b = 13 })); + AreEqual(13, entity.GetComponent().b); + IsTrue (entity.RemoveComponent()); + + IsTrue (entity.AddComponent(new MyComponent5())); + IsTrue (entity.RemoveComponent()); + IsTrue (entity.AddComponent(new MyComponent5 { b = 14 })); + AreEqual(14, entity.GetComponent().b); + IsTrue (entity.RemoveComponent()); + } + } } \ No newline at end of file