diff --git a/src/ECS/Archetype/Archetype.cs b/src/ECS/Archetype/Archetype.cs
index 72c6e863..980aabf5 100644
--- a/src/ECS/Archetype/Archetype.cs
+++ b/src/ECS/Archetype/Archetype.cs
@@ -308,20 +308,20 @@ internal static void MoveLastComponentsTo(Archetype arch, int newIndex, bool upd
}
///
- /// components are not copied.
+ /// components (child entities) are not copied.
/// Otherwise, two different entities would have the same child entities.
///
- internal static void CloneComponents(Archetype sourceArch, Archetype targetArch, in CopyContext context)
+ internal static void CopyComponents(Archetype sourceArch, Archetype targetArch, in CopyContext context, long updateIndexTypes)
{
var sourceIndex = context.source.compIndex;
var targetIndex = context.target.compIndex;
if (sourceArch == targetArch) {
foreach (var sourceHeap in sourceArch.structHeaps) {
- sourceHeap.CloneComponent(sourceIndex, sourceHeap, targetIndex, context);
+ sourceHeap.CopyComponent(sourceIndex, sourceHeap, targetIndex, context, updateIndexTypes);
}
} else {
foreach (var sourceHeap in sourceArch.structHeaps) {
- sourceHeap.CloneComponent(sourceIndex, targetArch.heapMap[sourceHeap.structIndex], targetIndex, context);
+ sourceHeap.CopyComponent(sourceIndex, targetArch.heapMap[sourceHeap.structIndex], targetIndex, context, updateIndexTypes);
}
}
}
diff --git a/src/ECS/Archetype/StructHeap.cs b/src/ECS/Archetype/StructHeap.cs
index bafbfcd1..8cc475bf 100644
--- a/src/ECS/Archetype/StructHeap.cs
+++ b/src/ECS/Archetype/StructHeap.cs
@@ -36,7 +36,7 @@ internal abstract class StructHeap : IComponentStash
internal abstract void ResizeComponents (int capacity, int count);
internal abstract void MoveComponent (int from, int to);
internal abstract void CopyComponentTo (int sourcePos, StructHeap targetHeap, int targetPos);
- internal abstract void CloneComponent (int sourcePos, StructHeap targetHeap, int targetPos, in CopyContext context);
+ internal abstract void CopyComponent (int sourcePos, StructHeap targetHeap, int targetPos, in CopyContext context, long updateIndexTypes);
internal abstract void SetComponentDefault (int compIndex);
internal abstract void SetComponentsDefault (int compIndexStart, int count);
internal abstract object GetComponentDebug (int compIndex);
diff --git a/src/ECS/Archetype/StructHeap.generic.cs b/src/ECS/Archetype/StructHeap.generic.cs
index a40a0aab..7da3bd73 100644
--- a/src/ECS/Archetype/StructHeap.generic.cs
+++ b/src/ECS/Archetype/StructHeap.generic.cs
@@ -73,24 +73,33 @@ internal override void CopyComponentTo(int sourcePos, StructHeap target, int tar
targetHeap.components[targetPos] = components[sourcePos];
}
- internal override void CloneComponent(int sourcePos, StructHeap targetHeap, int targetPos, in CopyContext context)
+ internal override void CopyComponent(int sourcePos, StructHeap targetHeap, int targetPos, in CopyContext context, long updateIndexTypes)
{
if (typeof(T) == typeof(TreeNode)) {
return;
}
- var copyValue = CopyValueUtils.CopyValue;
- ref T source = ref components[sourcePos];
- ref T target = ref ((StructHeap)targetHeap).components[targetPos];
+ var copyValue = CopyValueUtils.CopyValue;
+ ref T source = ref components[sourcePos];
+ var typedTargetHeap = (StructHeap)targetHeap;
+ ref T target = ref typedTargetHeap.components[targetPos];
+ if (StructInfo.HasIndex) {
+ AddOrUpdateIndex(source, target, context.target, typedTargetHeap, updateIndexTypes);
+ }
if (copyValue == null) {
target = source;
} else {
copyValue(source, ref target, context);
}
- if (!StructInfo.HasIndex) {
- return;
+ }
+
+ private static void AddOrUpdateIndex(in T source, in T target, in Entity targetEntity, StructHeap targetHeap, long updateIndexTypes)
+ {
+ if (((1 << StructInfo.Index) & updateIndexTypes) == 0) {
+ StoreIndex.AddIndex(targetEntity.store, targetEntity.Id, source);
+ } else {
+ targetHeap.componentStash = target;
+ StoreIndex.UpdateIndex(targetEntity.store, targetEntity.Id, source, targetHeap);
}
- var targetEntity = context.target;
- StoreIndex.AddIndex(targetEntity.store, targetEntity.Id, source);
}
internal override void SetComponentDefault (int compIndex) {
diff --git a/src/ECS/Entity/Extensions/EntityExtensions.cs b/src/ECS/Entity/Extensions/EntityExtensions.cs
index f2a49b8d..15c87a78 100644
--- a/src/ECS/Entity/Extensions/EntityExtensions.cs
+++ b/src/ECS/Entity/Extensions/EntityExtensions.cs
@@ -15,7 +15,7 @@ namespace Friflo.Engine.ECS;
public static partial class EntityExtensions
{
#region add components
- private static readonly long IndexTypesMask = EntityStoreBase.Static.EntitySchema.indexTypes.bitSet.l0;
+ internal static readonly long IndexTypesMask = EntityStoreBase.Static.EntitySchema.indexTypes.bitSet.l0;
private static void UpdateIndexedComponents(Entity entity, Archetype archetype, long indexTypesMask)
{
diff --git a/src/ECS/Entity/Store/Entities.cs b/src/ECS/Entity/Store/Entities.cs
index 9630943e..210f125a 100644
--- a/src/ECS/Entity/Store/Entities.cs
+++ b/src/ECS/Entity/Store/Entities.cs
@@ -76,6 +76,15 @@ internal int CreateEntityInternal(Archetype archetype, int id, out short revisio
return CreateEntityNode(archetype, id, out revision);
}
+ private static void RemoveIndexedComponents(Entity entity, long removedIndexTypes) {
+ var removeTypes = new ComponentTypes();
+ removeTypes.bitSet.l0 = removedIndexTypes;
+ var heapMap = entity.archetype.heapMap;
+ foreach (var removeType in removeTypes) {
+ heapMap[removeType.StructIndex].RemoveIndex(entity);
+ }
+ }
+
///
/// Copy all components, tags and scripts of the entity to the entity.
/// The and entities can be in the same or different stores.
@@ -89,14 +98,27 @@ public static void CopyEntity(Entity source, Entity target)
var sourceArch = source.GetArchetype() ?? throw EntityArgumentNullException(source, nameof(source));
var curTargetArch = target.GetArchetype() ?? throw EntityArgumentNullException(target, nameof(target));
var targetStore = target.store;
- var targetArch = targetStore.GetArchetype(sourceArch.componentTypes, sourceArch.Tags);
+ if (source.store == targetStore) {
+ if (targetStore.internBase.activeQueryLoops > 0) {
+ throw StructuralChangeWithinQueryLoop();
+ }
+ }
+ var targetArch = targetStore.GetArchetype(sourceArch.componentTypes, sourceArch.Tags);
if (targetArch != curTargetArch) {
+ var removedIndexTypes = (curTargetArch.componentTypes.bitSet.l0 & ~targetArch.componentTypes.bitSet.l0) & EntityExtensions.IndexTypesMask;
+ // --- remove indexes of removed indexed components
+ if (removedIndexTypes != 0) {
+ RemoveIndexedComponents(target, removedIndexTypes);
+ }
+ // --- move entity targetArch
ref var node = ref targetStore.nodes[target.Id];
node.compIndex = Archetype.MoveEntityTo(curTargetArch, target.Id, node.compIndex, targetArch);
node.archetype = targetArch;
}
- var context = new CopyContext(source, target);
- Archetype.CloneComponents(sourceArch, targetArch, context);
+ // bit == 1: update component index. bit == 0: add component index
+ var updateIndexTypes = curTargetArch.componentTypes.bitSet.l0 & targetArch.componentTypes.bitSet.l0;
+ var context = new CopyContext(source, target);
+ Archetype.CopyComponents(sourceArch, targetArch, context, updateIndexTypes);
targetStore.CloneScrips(source, target);
}
@@ -119,7 +141,7 @@ public Entity CloneEntity(Entity entity)
// if (true) {
var context = new CopyContext(entity, clone);
- Archetype.CloneComponents(archetype, archetype, context);
+ Archetype.CopyComponents(archetype, archetype, context, 0);
CloneScrips(entity, clone);
diff --git a/src/Tests/ECS/Arch/Test_StructuralChangeException.cs b/src/Tests/ECS/Arch/Test_StructuralChangeException.cs
index f941ae69..9bf7ec92 100644
--- a/src/Tests/ECS/Arch/Test_StructuralChangeException.cs
+++ b/src/Tests/ECS/Arch/Test_StructuralChangeException.cs
@@ -51,6 +51,11 @@ private static void TestExceptions(EntityStore store, Entity entity)
entity.RemoveComponent();
});
+ var target = store.CreateEntity();
+ Assert.Throws(() => {
+ EntityStore.CopyEntity(entity, target);
+ });
+
var buffer = store.GetCommandBuffer();
Assert.Throws(() => {
buffer.Playback();
diff --git a/src/Tests/ECS/Entity/Test_CopyEntity.cs b/src/Tests/ECS/Entity/Test_CopyEntity.cs
index b9a74595..fab76786 100644
--- a/src/Tests/ECS/Entity/Test_CopyEntity.cs
+++ b/src/Tests/ECS/Entity/Test_CopyEntity.cs
@@ -1,6 +1,7 @@
using System;
using Friflo.Engine.ECS;
using NUnit.Framework;
+using Tests.ECS.Index;
using static NUnit.Framework.Assert;
// ReSharper disable EqualExpressionComparison
@@ -11,6 +12,31 @@ namespace Tests.ECS {
public static class Test_CopyEntity
{
+ [Test]
+ public static void Test_CopyEntity_CloneStore_subset()
+ {
+ var store = new EntityStore();
+ var targetStore = new EntityStore();
+
+ store.CreateEntity(new Position(1,1,1)); // 1
+ store.CreateEntity(new Position(2,2,2), Tags.Get()); // 2
+ store.CreateEntity(new Position(3,3,3)); // 3
+ store.CreateEntity(new Position(4,4,4), Tags.Get()); // 4
+ store.CreateEntity(new Position(5,5,5)); // 5
+
+ // Query will copy only entities [2, 4]
+ var query = store.Query().AllTags(Tags.Get());
+ foreach (var entity in query.Entities) {
+ // preserve same entity ids in target store
+ if (!targetStore.TryGetEntityById(entity.Id, out Entity targetEntity)) {
+ targetEntity = targetStore.CreateEntity(entity.Id);
+ }
+ EntityStore.CopyEntity(entity, targetEntity);
+ }
+ AreEqual(new Position(2,2,2), targetStore.GetEntityById(2).GetComponent());
+ AreEqual(new Position(4,4,4), targetStore.GetEntityById(4).GetComponent());
+ }
+
[Test]
public static void Test_CopyEntity_different_stores()
{
@@ -68,6 +94,39 @@ public static void Test_CopyEntity_same_stores()
IsTrue(target6.Tags.Has());
}
+ [Test]
+ public static void Test_CopyEntity_IndexedComponent()
+ {
+ var store = new EntityStore();
+ var index = store.ComponentIndex();
+
+ var entity1 = store.CreateEntity(new IndexedInt { value = 11 });
+ var entity2 = store.CreateEntity(2);
+ AreEqual("{ 1 }", index[11].Debug());
+
+ EntityStore.CopyEntity(entity1, entity2);
+ AreEqual("{ 1, 2 }", index[11].Debug());
+ AreEqual(2, store.Count);
+ AreEqual(new IndexedInt { value = 11 }, entity2.GetComponent());
+
+ entity1.AddComponent(new IndexedInt { value = 42 });
+ AreEqual("{ 2 }", index[11].Debug());
+ AreEqual("{ 1 }", index[42].Debug());
+
+ EntityStore.CopyEntity(entity1, entity2);
+ AreEqual("{ }", index[11].Debug());
+ AreEqual("{ 1, 2 }", index[42].Debug());
+ AreEqual(new IndexedInt { value = 42 }, entity2.GetComponent());
+
+ entity1.RemoveComponent();
+ AreEqual("{ }", index[11].Debug());
+ AreEqual("{ 2 }", index[42].Debug());
+
+ EntityStore.CopyEntity(entity1, entity2);
+ AreEqual("{ }", index[11].Debug());
+ AreEqual("{ }", index[42].Debug());
+ }
+
[Test]
public static void Test_CopyEntity_exceptions()
{