Repository: mlange-42/arche
Pull request #260 (2023-05-02 19:16:33)

Bugfix: Move relation target when exchanging components
* Relation target got lost when moving between nodes/archetypes.
* Update archetype count in node stats update

coveralls: ## Pull Request Test Coverage Report for [Build 4864928303](https://coveralls.io/builds/59544085)

* **17** of **17**   **(100.0%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **100.0%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/59544085/badge)](https://coveralls.io/builds/59544085) |
| :-- | --: |
| Change from base [Build 4863558761](https://coveralls.io/builds/59538198): |  0.0% |
| Covered Lines: | 3541 |
| Relevant Lines: | 3541 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/ecs/archetype.go b/ecs/archetype.go
index 6d268a6..9a7ac86 100644
--- a/ecs/archetype.go
+++ b/ecs/archetype.go
@@ -93,7 +93,7 @@ func (a *archetypeNode) CreateArchetype(target Entity, components ...componentTy
 		a.archetypes.Add(archetype{})
 		archIndex := a.archetypes.Len() - 1
 		arch = a.archetypes.Get(archIndex)
-		arch.Init(a, archIndex, true, target, a.relation, components...)
+		arch.Init(a, archIndex, true, target, components...)
 	}
 	a.archetypeMap[target] = arch
 	return arch
@@ -204,6 +204,8 @@ func (a *archetypeNode) UpdateStats(stats *stats.NodeStats, reg *componentRegist
 	}
 
 	stats.IsActive = true
+	stats.ArchetypeCount = int(cntNew)
+	stats.ActiveArchetypeCount = int(cntNew) - len(a.freeIndices)
 	stats.Capacity = cap
 	stats.Size = count
 	stats.Memory = memory
@@ -281,7 +283,7 @@ type archetype struct {
 }
 
 // Init initializes an archetype
-func (a *archetype) Init(node *archetypeNode, index int32, forStorage bool, relation Entity, relationComp int8, components ...componentType) {
+func (a *archetype) Init(node *archetypeNode, index int32, forStorage bool, relation Entity, components ...componentType) {
 	var mask Mask
 	if !node.IsActive() {
 		node.Ids = make([]ID, len(components))
@@ -337,7 +339,7 @@ func (a *archetype) Init(node *archetypeNode, index int32, forStorage bool, rela
 		entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
 		Mask:              mask,
 		Relation:          relation,
-		RelationComponent: relationComp,
+		RelationComponent: node.relation,
 	}
 
 	a.graphNode = node
diff --git a/ecs/world.go b/ecs/world.go
index 166fe4c..34ea2bc 100644
--- a/ecs/world.go
+++ b/ecs/world.go
@@ -114,7 +114,7 @@ func fromConfig(conf Config) World {
 		filterCache:   newCache(),
 	}
 	node := w.createArchetypeNode(Mask{}, -1)
-	w.createArchetype(node, Entity{}, 0, false)
+	w.createArchetype(node, Entity{}, false)
 	return w
 }
 
@@ -139,7 +139,7 @@ func (w *World) NewEntity(comps ...ID) Entity {
 
 	arch := w.archetypes.Get(0)
 	if len(comps) > 0 {
-		arch = w.findOrCreateArchetype(arch, comps, nil, Entity{}, -1)
+		arch = w.findOrCreateArchetype(arch, comps, nil, Entity{})
 	}
 
 	entity := w.createEntity(arch)
@@ -174,7 +174,7 @@ func (w *World) NewEntityWith(comps ...Component) Entity {
 	}
 
 	arch := w.archetypes.Get(0)
-	arch = w.findOrCreateArchetype(arch, ids, nil, Entity{}, -1)
+	arch = w.findOrCreateArchetype(arch, ids, nil, Entity{})
 
 	entity := w.createEntity(arch)
 
@@ -199,7 +199,7 @@ func (w *World) newEntityTarget(targetID ID, target Entity, comps ...ID) Entity
 	arch := w.archetypes.Get(0)
 
 	if len(comps) > 0 {
-		arch = w.findOrCreateArchetype(arch, comps, nil, target, int8(targetID))
+		arch = w.findOrCreateArchetype(arch, comps, nil, target)
 	}
 	w.checkRelation(arch, targetID)
 
@@ -229,7 +229,7 @@ func (w *World) newEntityTargetWith(targetID ID, target Entity, comps ...Compone
 	}
 
 	arch := w.archetypes.Get(0)
-	arch = w.findOrCreateArchetype(arch, ids, nil, target, int8(targetID))
+	arch = w.findOrCreateArchetype(arch, ids, nil, target)
 	w.checkRelation(arch, targetID)
 
 	entity := w.createEntity(arch)
@@ -593,7 +593,7 @@ func (w *World) Exchange(entity Entity, add []ID, rem []ID) {
 
 	oldIDs := oldArch.Components()
 
-	arch := w.findOrCreateArchetype(oldArch, add, rem, Entity{}, -1)
+	arch := w.findOrCreateArchetype(oldArch, add, rem, oldArch.Relation)
 	newIndex := arch.Alloc(entity)
 
 	for _, id := range oldIDs {
@@ -677,7 +677,7 @@ func (w *World) setRelation(entity Entity, comp ID, target Entity) {
 
 	arch := oldArch.graphNode.GetArchetype(target)
 	if arch == nil {
-		arch = w.createArchetype(oldArch.graphNode, target, int8(oldArch.graphNode.relation), true)
+		arch = w.createArchetype(oldArch.graphNode, target, true)
 	}
 
 	newIndex := arch.Alloc(entity)
@@ -901,7 +901,7 @@ func (w *World) newEntitiesNoNotify(count int, targetID int8, target Entity, com
 
 	arch := w.archetypes.Get(0)
 	if len(comps) > 0 {
-		arch = w.findOrCreateArchetype(arch, comps, nil, target, targetID)
+		arch = w.findOrCreateArchetype(arch, comps, nil, target)
 	}
 	if targetID >= 0 {
 		w.checkRelation(arch, uint8(targetID))
@@ -936,7 +936,7 @@ func (w *World) newEntitiesWithNoNotify(count int, targetID int8, target Entity,
 
 	arch := w.archetypes.Get(0)
 	if len(comps) > 0 {
-		arch = w.findOrCreateArchetype(arch, ids, nil, target, targetID)
+		arch = w.findOrCreateArchetype(arch, ids, nil, target)
 	}
 	if targetID >= 0 {
 		w.checkRelation(arch, uint8(targetID))
@@ -1017,10 +1017,10 @@ func (w *World) copyTo(entity Entity, id ID, comp interface{}) unsafe.Pointer {
 // Tries to find an archetype by traversing the archetype graph,
 // searching by mask and extending the graph if necessary.
 // A new archetype is created for the final graph node if not already present.
-func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID, target Entity, targetComponent int8) *archetype {
+func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID, target Entity) *archetype {
 	curr := start.graphNode
 	mask := start.Mask
-	relation := start.graphNode.relation
+	relation := start.RelationComponent
 	for _, id := range rem {
 		mask.Set(id, false)
 		if w.registry.IsRelation.Get(id) {
@@ -1054,7 +1054,7 @@ func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID, targ
 	}
 	arch := curr.GetArchetype(target)
 	if arch == nil {
-		arch = w.createArchetype(curr, target, targetComponent, true)
+		arch = w.createArchetype(curr, target, true)
 	}
 	return arch
 }
@@ -1097,7 +1097,7 @@ func (w *World) createArchetypeNode(mask Mask, relation int8) *archetypeNode {
 // Creates an archetype for the given archetype graph node.
 // Initializes the archetype with a capacity according to CapacityIncrement if forStorage is true,
 // and with a capacity of 1 otherwise.
-func (w *World) createArchetype(node *archetypeNode, target Entity, targetComponent int8, forStorage bool) *archetype {
+func (w *World) createArchetype(node *archetypeNode, target Entity, forStorage bool) *archetype {
 	mask := node.mask
 	count := int(mask.TotalBitsSet())
 	types := make([]componentType, count)
@@ -1127,7 +1127,7 @@ func (w *World) createArchetype(node *archetypeNode, target Entity, targetCompon
 		w.archetypes.Add(archetype{})
 		archIndex := w.archetypes.Len() - 1
 		arch = w.archetypes.Get(archIndex)
-		arch.Init(node, archIndex, forStorage, target, targetComponent, types...)
+		arch.Init(node, archIndex, forStorage, target, types...)
 		node.SetArchetype(arch)
 	}
 
diff --git a/examples/relations/main.go b/examples/relations/main.go
index f5ed4d3..6af40e1 100644
--- a/examples/relations/main.go
+++ b/examples/relations/main.go
@@ -34,7 +34,12 @@ func run() {
 	parent2 := world.NewEntity()
 
 	// Create an entity with a ChildOf relation to a parent entity.
-	childBuilder.New(parent1)
+	child := childBuilder.New(parent1)
+	// Change the child's relation target.
+	world.Relations().Set(child, childID, parent2)
+	// Get the child's relation target.
+	fmt.Println(world.Relations().Get(child, childID))
+
 	// Create entities with a relation in batches.
 	childBuilder.NewBatch(10, parent1)
 	childBuilder.NewBatch(10, parent2)
@@ -70,7 +75,14 @@ func runGeneric() {
 	parent2 := world.NewEntity()
 
 	// Create an entity with a ChildOf relation to a parent entity.
-	childBuilder.New(parent1)
+	child := childBuilder.New(parent1)
+	// To set or get the relation target via the world, a Map is required.
+	relationMap := generic.NewMap[ChildOf](&world)
+	// Change the child's relation target.
+	relationMap.SetRelation(child, parent2)
+	// Get the child's relation target.
+	fmt.Println(relationMap.GetRelation(child))
+
 	// Create entities with a relation in batches.
 	childBuilder.NewBatch(10, parent1)
 	childBuilder.NewBatch(10, parent2)


Test patch:
diff --git a/ecs/archetype_test.go b/ecs/archetype_test.go
index 3ffed5e..f14e604 100644
--- a/ecs/archetype_test.go
+++ b/ecs/archetype_test.go
@@ -15,7 +15,7 @@ func TestArchetype(t *testing.T) {
 
 	node := newArchetypeNode(All(0, 1), -1, 32)
 	arch := archetype{}
-	arch.Init(&node, 0, false, Entity{}, -1, comps...)
+	arch.Init(&node, 0, false, Entity{}, comps...)
 
 	arch.Add(
 		newEntity(0),
@@ -73,11 +73,11 @@ func TestNewArchetype(t *testing.T) {
 
 	node := newArchetypeNode(All(0, 1), -1, 32)
 	arch := archetype{}
-	arch.Init(&node, 0, true, Entity{}, -1, comps...)
+	arch.Init(&node, 0, true, Entity{}, comps...)
 	assert.Equal(t, 32, int(arch.Cap()))
 
 	arch = archetype{}
-	arch.Init(&node, 0, false, Entity{}, -1, comps...)
+	arch.Init(&node, 0, false, Entity{}, comps...)
 	assert.Equal(t, 1, int(arch.Cap()))
 
 	comps = []componentType{
@@ -87,7 +87,7 @@ func TestNewArchetype(t *testing.T) {
 	assert.Panics(t, func() {
 		node := newArchetypeNode(All(0, 1), -1, 32)
 		arch := archetype{}
-		arch.Init(&node, 0, true, Entity{}, -1, comps...)
+		arch.Init(&node, 0, true, Entity{}, comps...)
 	})
 }
 
@@ -99,7 +99,7 @@ func TestArchetypeExtend(t *testing.T) {
 
 	node := newArchetypeNode(All(0, 1), -1, 8)
 	arch := archetype{}
-	arch.Init(&node, 0, true, Entity{}, -1, comps...)
+	arch.Init(&node, 0, true, Entity{}, comps...)
 
 	assert.Equal(t, 8, int(arch.Cap()))
 	assert.Equal(t, 0, int(arch.Len()))
@@ -121,7 +121,7 @@ func TestArchetypeAlloc(t *testing.T) {
 	}
 	node := newArchetypeNode(All(0, 1), -1, 8)
 	arch := archetype{}
-	arch.Init(&node, 0, true, Entity{}, -1, comps...)
+	arch.Init(&node, 0, true, Entity{}, comps...)
 
 	assert.Equal(t, 8, int(arch.Cap()))
 	assert.Equal(t, 0, int(arch.Len()))
@@ -146,7 +146,7 @@ func TestArchetypeAddGetSet(t *testing.T) {
 
 	node := newArchetypeNode(All(0, 1), -1, 1)
 	a := archetype{}
-	a.Init(&node, 0, true, Entity{}, -1, comps...)
+	a.Init(&node, 0, true, Entity{}, comps...)
 
 	assert.Equal(t, 1, int(a.Cap()))
 	assert.Equal(t, 0, int(a.Len()))
@@ -181,7 +181,7 @@ func TestArchetypeReset(t *testing.T) {
 
 	node := newArchetypeNode(All(0, 1), -1, 32)
 	arch := archetype{}
-	arch.Init(&node, 0, false, Entity{}, -1, comps...)
+	arch.Init(&node, 0, false, Entity{}, comps...)
 
 	arch.Add(
 		newEntity(0),
@@ -227,7 +227,7 @@ func TestArchetypeZero(t *testing.T) {
 
 	node := newArchetypeNode(All(0, 1), -1, 32)
 	arch := archetype{}
-	arch.Init(&node, 0, false, Entity{}, -1, comps...)
+	arch.Init(&node, 0, false, Entity{}, comps...)
 
 	arch.Alloc(newEntity(0))
 	arch.Alloc(newEntity(1))
@@ -259,7 +259,7 @@ func BenchmarkIterArchetype_1000(b *testing.B) {
 
 	node := newArchetypeNode(All(0), -1, 32)
 	arch := archetype{}
-	arch.Init(&node, 0, true, Entity{}, -1, comps...)
+	arch.Init(&node, 0, true, Entity{}, comps...)
 
 	for i := 0; i < 1000; i++ {
 		arch.Alloc(newEntity(eid(i)))
diff --git a/ecs/world_test.go b/ecs/world_test.go
index f0be920..3fd957c 100644
--- a/ecs/world_test.go
+++ b/ecs/world_test.go
@@ -902,6 +902,57 @@ func TestWorldRelationCreate(t *testing.T) {
 	})
 }
 
+func TestWorldRelationMove(t *testing.T) {
+	world := NewWorld()
+	world.SetListener(func(e *EntityEvent) {})
+
+	posID := ComponentID[Position](&world)
+	relID := ComponentID[testRelationA](&world)
+
+	target1 := world.NewEntity()
+	target2 := world.NewEntity()
+
+	entities := []Entity{}
+	for _, trg := range [...]Entity{target1, target2} {
+		query := NewBuilder(&world, relID).WithRelation(relID).NewQuery(100, trg)
+		for query.Next() {
+			entities = append(entities, query.Entity())
+		}
+	}
+
+	for _, e := range entities {
+		world.Add(e, posID)
+	}
+
+	for i, e := range entities {
+		trg := world.Relations().Get(e, relID)
+
+		if i < 100 {
+			assert.Equal(t, target1, trg)
+		} else {
+			assert.Equal(t, target2, trg)
+		}
+	}
+
+	for _, e := range entities {
+		world.Remove(e, posID)
+	}
+
+	for i, e := range entities {
+		trg := world.Relations().Get(e, relID)
+
+		if i < 100 {
+			assert.Equal(t, target1, trg)
+		} else {
+			assert.Equal(t, target2, trg)
+		}
+	}
+
+	for _, e := range entities {
+		world.Remove(e, relID)
+	}
+}
+
 func TestWorldLock(t *testing.T) {
 	world := NewWorld()
 
@@ -1148,23 +1199,23 @@ func TestArchetypeGraph(t *testing.T) {
 	rotID := ComponentID[rotation](&world)
 
 	archEmpty := world.archetypes.Get(0)
-	arch0 := world.findOrCreateArchetype(archEmpty, []ID{posID, velID}, []ID{}, Entity{}, -1)
-	archEmpty2 := world.findOrCreateArchetype(arch0, []ID{}, []ID{velID, posID}, Entity{}, -1)
+	arch0 := world.findOrCreateArchetype(archEmpty, []ID{posID, velID}, []ID{}, Entity{})
+	archEmpty2 := world.findOrCreateArchetype(arch0, []ID{}, []ID{velID, posID}, Entity{})
 	assert.Equal(t, archEmpty, archEmpty2)
 	assert.Equal(t, int32(2), world.archetypes.Len())
 	assert.Equal(t, int32(3), world.graph.Len())
 
-	archEmpty3 := world.findOrCreateArchetype(arch0, []ID{}, []ID{posID, velID}, Entity{}, -1)
+	archEmpty3 := world.findOrCreateArchetype(arch0, []ID{}, []ID{posID, velID}, Entity{})
 	assert.Equal(t, archEmpty, archEmpty3)
 	assert.Equal(t, int32(2), world.archetypes.Len())
 	assert.Equal(t, int32(4), world.graph.Len())
 
-	arch01 := world.findOrCreateArchetype(arch0, []ID{velID}, []ID{}, Entity{}, -1)
-	arch012 := world.findOrCreateArchetype(arch01, []ID{rotID}, []ID{}, Entity{}, -1)
+	arch01 := world.findOrCreateArchetype(arch0, []ID{velID}, []ID{}, Entity{})
+	arch012 := world.findOrCreateArchetype(arch01, []ID{rotID}, []ID{}, Entity{})
 
 	assert.Equal(t, []ID{0, 1, 2}, arch012.graphNode.Ids)
 
-	archEmpty4 := world.findOrCreateArchetype(arch012, []ID{}, []ID{posID, rotID, velID}, Entity{}, -1)
+	archEmpty4 := world.findOrCreateArchetype(arch012, []ID{}, []ID{posID, rotID, velID}, Entity{})
 	assert.Equal(t, archEmpty, archEmpty4)
 }