Repository: mlange-42/arche
Pull request #348 (2024-01-19 21:18:55)

Batch operations return the number of affected entities

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


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

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/65188661/badge)](https://coveralls.io/builds/65188661) |
| :-- | --: |
| Change from base [Build 7589253149](https://coveralls.io/builds/65188569): |  0.0% |
| Covered Lines: | 6151 |
| Relevant Lines: | 6151 |

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


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9f071e..f0cd189 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,9 +13,9 @@
 
 * `MaskTotalBits` changed from 128 to 256 (#313)
 * Removes `Mask.Lo` and `Mask.Hi`, internal mask representation is now private (#313)
-* `Filters.Matches(Mask)` became `Filters.Matches(*Mask)`; same for all `Filter` implementations (#313)  
+* `Filter.Matches(Mask)` became `Filter.Matches(*Mask)`; same for all `Filter` implementations (#313)  
 This change was necessary to get the same performance as before, despite the more heavyweight implementation of the now 256 bits `Mask`.
-* Component and resource IDs are now opaque types instead of type aliases for `uint8` (#329)
+* Component and resource IDs are now opaque types instead of type aliases for `uint8` (#330)
 * Restructures `EntityEvent` to remove redundant information and better handle relation changes (#333)
 * World event listener changed from a simple function to a `Listener` interface (#334)
 * Removes `World.ComponentType(ID)`, use function `ComponentInfo(ID)` instead (#341)
@@ -26,7 +26,7 @@ This change was necessary to get the same performance as before, despite the mor
 * Adds methods `World.Ids(Entity)` and `Query.Ids()` to get component IDs for an entity (#315, #325)
 * Entities support JSON marshalling and unmarshalling (#319)
 * The world's entity state can be extracted and re-established via `World.DumpEntities()` and `World.LoadEntities()` (#319, #326)
-* Adds functions `ComponentIDs(*World)` and `ResourceIDs(*World)` to get all registered IDs (#329)
+* Adds functions `ComponentIDs(*World)` and `ResourceIDs(*World)` to get all registered IDs (#330)
 * Adds methods `Mask.And`, `Mask.Or` and `Mask.Xor` (#335)
 * Adds build tag `tiny` to restrict to 64 components for an extra bit of performance (#338)
 * Adds methods `Relations.Exchange()`, `Relations.ExchangeBatch()`, `Relations.ExchangeBatchQ()` for exchange with relation target (#342)
@@ -35,6 +35,7 @@ This change was necessary to get the same performance as before, despite the mor
 * Generic API adds optional relation target argument to most `MapX` methods (#342)
 * Generic API adds `FilterX.Filter()` to get an `ecs.Filter` from a generic one (#342)
 * Generic API adds `Map.SetRelationBatch()` and `Map.SetRelationBatchQ()` (#344)
+* All batch operations (except entity creation) return the number of affected entities (#348)
 
 ### Performance
 
diff --git a/ecs/batch.go b/ecs/batch.go
index 6b6efbf..15fc7ce 100644
--- a/ecs/batch.go
+++ b/ecs/batch.go
@@ -8,14 +8,15 @@ type Batch struct {
 }
 
 // Add adds components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // Panics:
 //   - when called with components that can't be added because they are already present.
 //   - when called on a locked world. Do not use during [Query] iteration!
 //
 // See also [Batch.AddQ] and [World.Add].
-func (b *Batch) Add(filter Filter, comps ...ID) {
-	b.world.exchangeBatch(filter, comps, nil, ID{}, false, Entity{})
+func (b *Batch) Add(filter Filter, comps ...ID) int {
+	return b.world.exchangeBatch(filter, comps, nil, ID{}, false, Entity{})
 }
 
 // AddQ adds components to many entities, matching a filter.
@@ -31,14 +32,15 @@ func (b *Batch) AddQ(filter Filter, comps ...ID) Query {
 }
 
 // Remove removes components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // Panics:
 //   - when called with components that can't be removed because they are not present.
 //   - when called on a locked world. Do not use during [Query] iteration!
 //
 // See also [Batch.RemoveQ] and [World.Remove].
-func (b *Batch) Remove(filter Filter, comps ...ID) {
-	b.world.exchangeBatch(filter, nil, comps, ID{}, false, Entity{})
+func (b *Batch) Remove(filter Filter, comps ...ID) int {
+	return b.world.exchangeBatch(filter, nil, comps, ID{}, false, Entity{})
 }
 
 // RemoveQ removes components from many entities, matching a filter.
@@ -54,6 +56,7 @@ func (b *Batch) RemoveQ(filter Filter, comps ...ID) Query {
 }
 
 // SetRelation sets the [Relation] target for many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // Entities that match the filter but already have the desired target entity are not processed,
 // and no events are emitted for them.
@@ -64,8 +67,8 @@ func (b *Batch) RemoveQ(filter Filter, comps ...ID) Query {
 //   - when called on a locked world. Do not use during [Query] iteration!
 //
 // See also [Relations.Set] and [Relations.SetBatch].
-func (b *Batch) SetRelation(filter Filter, comp ID, target Entity) {
-	b.world.setRelationBatch(filter, comp, target)
+func (b *Batch) SetRelation(filter Filter, comp ID, target Entity) int {
+	return b.world.setRelationBatch(filter, comp, target)
 }
 
 // SetRelationQ sets the [Relation] target for many entities, matching a filter.
@@ -85,6 +88,7 @@ func (b *Batch) SetRelationQ(filter Filter, comp ID, target Entity) Query {
 }
 
 // Exchange exchanges components for many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // When a [Relation] component is removed and another one is added,
 // the target entity of the relation is reset to zero.
@@ -95,8 +99,8 @@ func (b *Batch) SetRelationQ(filter Filter, comp ID, target Entity) Query {
 //
 // See also [Batch.ExchangeQ] and [World.Exchange].
 // For batch-exchange with a relation target, see [Relations.ExchangeBatch].
-func (b *Batch) Exchange(filter Filter, add []ID, rem []ID) {
-	b.world.exchangeBatch(filter, add, rem, ID{}, false, Entity{})
+func (b *Batch) Exchange(filter Filter, add []ID, rem []ID) int {
+	return b.world.exchangeBatch(filter, add, rem, ID{}, false, Entity{})
 }
 
 // ExchangeQ exchanges components for many entities, matching a filter.
diff --git a/ecs/relations.go b/ecs/relations.go
index 8d5cb9e..ed9cee5 100644
--- a/ecs/relations.go
+++ b/ecs/relations.go
@@ -44,6 +44,7 @@ func (r *Relations) Set(entity Entity, comp ID, target Entity) {
 }
 
 // SetBatch sets the [Relation] target for many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // Panics:
 //   - when called for a missing component.
@@ -51,8 +52,8 @@ func (r *Relations) Set(entity Entity, comp ID, target Entity) {
 //   - when called on a locked world. Do not use during [Query] iteration!
 //
 // See also [Relations.Set], [Relations.SetBatchQ] and [Batch.SetRelation].
-func (r *Relations) SetBatch(filter Filter, comp ID, target Entity) {
-	r.world.setRelationBatch(filter, comp, target)
+func (r *Relations) SetBatch(filter Filter, comp ID, target Entity) int {
+	return r.world.setRelationBatch(filter, comp, target)
 }
 
 // SetBatchQ sets the [Relation] target for many entities, matching a filter.
@@ -89,6 +90,7 @@ func (r *Relations) Exchange(entity Entity, add []ID, rem []ID, relation ID, tar
 }
 
 // ExchangeBatch exchanges components for many entities, matching a filter.
+// Returns the number of affected entities.
 // In contrast to [Batch.Exchange], it allows to also set a relation target.
 //
 // When a [Relation] component is removed and another one is added,
@@ -102,8 +104,8 @@ func (r *Relations) Exchange(entity Entity, add []ID, rem []ID, relation ID, tar
 //   - when called on a locked world. Do not use during [Query] iteration!
 //
 // See also [Batch.Exchange], [Batch.ExchangeQ], [Relations.ExchangeBatch] and [World.Exchange].
-func (r *Relations) ExchangeBatch(filter Filter, add []ID, rem []ID, relation ID, target Entity) {
-	r.world.exchangeBatch(filter, add, rem, relation, true, target)
+func (r *Relations) ExchangeBatch(filter Filter, add []ID, rem []ID, relation ID, target Entity) int {
+	return r.world.exchangeBatch(filter, add, rem, relation, true, target)
 }
 
 // ExchangeBatchQ exchanges components for many entities, matching a filter.
diff --git a/ecs/world_internal.go b/ecs/world_internal.go
index 1fa9cfe..88c2fa3 100644
--- a/ecs/world_internal.go
+++ b/ecs/world_internal.go
@@ -536,17 +536,18 @@ func (w *World) getExchangeMask(mask Mask, add []ID, rem []ID) Mask {
 //   - when called on a locked world. Do not use during [Query] iteration!
 //
 // See also [World.Exchange].
-func (w *World) exchangeBatch(filter Filter, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) {
+func (w *World) exchangeBatch(filter Filter, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) int {
 	batches := batchArchetypes{
 		Added:   add,
 		Removed: rem,
 	}
 
-	w.exchangeBatchNoNotify(filter, add, rem, relation, hasRelation, target, &batches)
+	count := w.exchangeBatchNoNotify(filter, add, rem, relation, hasRelation, target, &batches)
 
 	if w.listener != nil {
 		w.notifyQuery(&batches)
 	}
+	return count
 }
 
 func (w *World) exchangeBatchQuery(filter Filter, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) Query {
@@ -561,20 +562,22 @@ func (w *World) exchangeBatchQuery(filter Filter, add []ID, rem []ID, relation I
 	return newBatchQuery(w, lock, &batches)
 }
 
-func (w *World) exchangeBatchNoNotify(filter Filter, add []ID, rem []ID, relation ID, hasRelation bool, target Entity, batches *batchArchetypes) {
+func (w *World) exchangeBatchNoNotify(filter Filter, add []ID, rem []ID, relation ID, hasRelation bool, target Entity, batches *batchArchetypes) int {
 	w.checkLocked()
 
 	if len(add) == 0 && len(rem) == 0 {
 		if hasRelation {
 			panic("exchange operation has no effect, but a relation is specified. Use Batch.SetRelation instead")
 		}
-		return
+		return 0
 	}
 
 	arches := w.getArchetypes(filter)
 	lengths := make([]uint32, len(arches))
+	var totalEntities uint32 = 0
 	for i, arch := range arches {
 		lengths[i] = arch.Len()
+		totalEntities += arch.Len()
 	}
 
 	for i, arch := range arches {
@@ -587,6 +590,8 @@ func (w *World) exchangeBatchNoNotify(filter Filter, add []ID, rem []ID, relatio
 		newArch, start := w.exchangeArch(arch, archLen, add, rem, relation, hasRelation, target)
 		batches.Add(newArch, arch, start, newArch.Len())
 	}
+
+	return int(totalEntities)
 }
 
 func (w *World) exchangeArch(oldArch *archetype, oldArchLen uint32, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) (*archetype, uint32) {
@@ -744,12 +749,13 @@ func (w *World) setRelation(entity Entity, comp ID, target Entity) {
 }
 
 // set relation target in batches.
-func (w *World) setRelationBatch(filter Filter, comp ID, target Entity) {
+func (w *World) setRelationBatch(filter Filter, comp ID, target Entity) int {
 	batches := batchArchetypes{}
-	w.setRelationBatchNoNotify(filter, comp, target, &batches)
+	count := w.setRelationBatchNoNotify(filter, comp, target, &batches)
 	if w.listener != nil && w.listener.Subscriptions().Contains(event.TargetChanged) {
 		w.notifyQuery(&batches)
 	}
+	return count
 }
 
 func (w *World) setRelationBatchQuery(filter Filter, comp ID, target Entity) Query {
@@ -759,7 +765,7 @@ func (w *World) setRelationBatchQuery(filter Filter, comp ID, target Entity) Que
 	return newBatchQuery(w, lock, &batches)
 }
 
-func (w *World) setRelationBatchNoNotify(filter Filter, comp ID, target Entity, batches *batchArchetypes) {
+func (w *World) setRelationBatchNoNotify(filter Filter, comp ID, target Entity, batches *batchArchetypes) int {
 	w.checkLocked()
 
 	if !target.IsZero() && !w.entityPool.Alive(target) {
@@ -768,8 +774,10 @@ func (w *World) setRelationBatchNoNotify(filter Filter, comp ID, target Entity,
 
 	arches := w.getArchetypes(filter)
 	lengths := make([]uint32, len(arches))
+	var totalEntities uint32 = 0
 	for i, arch := range arches {
 		lengths[i] = arch.Len()
+		totalEntities += arch.Len()
 	}
 
 	for i, arch := range arches {
@@ -786,6 +794,7 @@ func (w *World) setRelationBatchNoNotify(filter Filter, comp ID, target Entity,
 		newArch, start, end := w.setRelationArch(arch, archLen, comp, target)
 		batches.Add(newArch, arch, start, end)
 	}
+	return int(totalEntities)
 }
 
 func (w *World) setRelationArch(oldArch *archetype, oldArchLen uint32, comp ID, target Entity) (*archetype, uint32, uint32) {
diff --git a/generic/exchange.go b/generic/exchange.go
index 897ac93..bc75621 100644
--- a/generic/exchange.go
+++ b/generic/exchange.go
@@ -134,6 +134,7 @@ func (m *Exchange) Exchange(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // ExchangeBatch exchanges components on many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // Removes the components set via [Exchange.Removes].
 // Adds the components set via [Exchange.Adds].
@@ -143,13 +144,13 @@ func (m *Exchange) Exchange(entity ecs.Entity, target ...ecs.Entity) {
 // the target entity of the relation is set to zero if no target is given.
 //
 // See also [ecs.Batch.Exchange] and [ecs.Batch.ExchangeQ].
-func (m *Exchange) ExchangeBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Exchange) ExchangeBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Exchange has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.add, m.remove, m.relationID, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.add, m.remove, m.relationID, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.add, m.remove)
+		return m.world.Batch().Exchange(filter, m.add, m.remove)
 	}
 }
diff --git a/generic/generate/map.go.txt b/generic/generate/map.go.txt
index fd9eb41..e3d2448 100644
--- a/generic/generate/map.go.txt
+++ b/generic/generate/map.go.txt
@@ -126,18 +126,19 @@ func (m *Map{{ .Index }}{{ .Types }}) Add(entity ecs.Entity, target ...ecs.Entit
 }
 
 // AddBatch adds the Map{{ .Index }}'s components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map{{ .Index }}'s [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map{{ .Index }}{{ .Types }}) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map{{ .Index }}{{ .Types }}) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map{{ .Index }} has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -192,18 +193,19 @@ func (m *Map{{ .Index }}{{ .Types }}) Remove(entity ecs.Entity, target ...ecs.En
 }
 
 // RemoveBatch removes the Map{{ .Index }}'s components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map{{ .Index }}'s [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map{{ .Index }}{{ .Types }}) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map{{ .Index }}{{ .Types }}) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map{{ .Index }} has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
diff --git a/generic/map.go b/generic/map.go
index 6c3a54d..3f288fb 100644
--- a/generic/map.go
+++ b/generic/map.go
@@ -108,13 +108,14 @@ func (m *Map[T]) SetRelation(entity, target ecs.Entity) {
 }
 
 // SetRelationBatch sets the target entity for many entities and the Map's component.
+// Returns the number of affected entities.
 //
 // Panics if the entity does not have a component of that type.
 // Panics if the component is not a relation.
 //
 // See also [ecs.Batch.SetRelation].
-func (m *Map[T]) SetRelationBatch(filter ecs.Filter, target ecs.Entity) {
-	m.world.Batch().SetRelation(filter, m.id, target)
+func (m *Map[T]) SetRelationBatch(filter ecs.Filter, target ecs.Entity) int {
+	return m.world.Batch().SetRelation(filter, m.id, target)
 }
 
 // SetRelationBatch sets the target entity for many entities and the Map's component,
diff --git a/generic/map_generated.go b/generic/map_generated.go
index 8f2c758..b6acd18 100644
--- a/generic/map_generated.go
+++ b/generic/map_generated.go
@@ -133,18 +133,19 @@ func (m *Map1[A]) Add(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // AddBatch adds the Map1's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map1's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map1[A]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map1[A]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map1 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -199,18 +200,19 @@ func (m *Map1[A]) Remove(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // RemoveBatch removes the Map1's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map1's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map1[A]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map1[A]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map1 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -389,18 +391,19 @@ func (m *Map2[A, B]) Add(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // AddBatch adds the Map2's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map2's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map2[A, B]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map2[A, B]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map2 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -457,18 +460,19 @@ func (m *Map2[A, B]) Remove(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // RemoveBatch removes the Map2's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map2's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map2[A, B]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map2[A, B]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map2 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -654,18 +658,19 @@ func (m *Map3[A, B, C]) Add(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // AddBatch adds the Map3's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map3's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map3[A, B, C]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map3[A, B, C]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map3 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -724,18 +729,19 @@ func (m *Map3[A, B, C]) Remove(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // RemoveBatch removes the Map3's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map3's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map3[A, B, C]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map3[A, B, C]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map3 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -928,18 +934,19 @@ func (m *Map4[A, B, C, D]) Add(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // AddBatch adds the Map4's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map4's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map4[A, B, C, D]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map4[A, B, C, D]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map4 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -1000,18 +1007,19 @@ func (m *Map4[A, B, C, D]) Remove(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // RemoveBatch removes the Map4's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map4's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map4[A, B, C, D]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map4[A, B, C, D]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map4 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -1211,18 +1219,19 @@ func (m *Map5[A, B, C, D, E]) Add(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // AddBatch adds the Map5's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map5's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map5[A, B, C, D, E]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map5[A, B, C, D, E]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map5 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -1285,18 +1294,19 @@ func (m *Map5[A, B, C, D, E]) Remove(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // RemoveBatch removes the Map5's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map5's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map5[A, B, C, D, E]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map5[A, B, C, D, E]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map5 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -1503,18 +1513,19 @@ func (m *Map6[A, B, C, D, E, F]) Add(entity ecs.Entity, target ...ecs.Entity) {
 }
 
 // AddBatch adds the Map6's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map6's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map6[A, B, C, D, E, F]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map6[A, B, C, D, E, F]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map6 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -1579,18 +1590,19 @@ func (m *Map6[A, B, C, D, E, F]) Remove(entity ecs.Entity, target ...ecs.Entity)
 }
 
 // RemoveBatch removes the Map6's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map6's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map6[A, B, C, D, E, F]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map6[A, B, C, D, E, F]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map6 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -1804,18 +1816,19 @@ func (m *Map7[A, B, C, D, E, F, G]) Add(entity ecs.Entity, target ...ecs.Entity)
 }
 
 // AddBatch adds the Map7's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map7's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map7[A, B, C, D, E, F, G]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map7[A, B, C, D, E, F, G]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map7 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -1882,18 +1895,19 @@ func (m *Map7[A, B, C, D, E, F, G]) Remove(entity ecs.Entity, target ...ecs.Enti
 }
 
 // RemoveBatch removes the Map7's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map7's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map7[A, B, C, D, E, F, G]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map7[A, B, C, D, E, F, G]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map7 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -2114,18 +2128,19 @@ func (m *Map8[A, B, C, D, E, F, G, H]) Add(entity ecs.Entity, target ...ecs.Enti
 }
 
 // AddBatch adds the Map8's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map8's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map8[A, B, C, D, E, F, G, H]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map8[A, B, C, D, E, F, G, H]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map8 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -2194,18 +2209,19 @@ func (m *Map8[A, B, C, D, E, F, G, H]) Remove(entity ecs.Entity, target ...ecs.E
 }
 
 // RemoveBatch removes the Map8's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map8's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map8[A, B, C, D, E, F, G, H]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map8[A, B, C, D, E, F, G, H]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map8 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -2433,18 +2449,19 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) Add(entity ecs.Entity, target ...ecs.E
 }
 
 // AddBatch adds the Map9's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map9's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map9[A, B, C, D, E, F, G, H, I]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map9[A, B, C, D, E, F, G, H, I]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map9 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -2515,18 +2532,19 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) Remove(entity ecs.Entity, target ...ec
 }
 
 // RemoveBatch removes the Map9's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map9's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map9[A, B, C, D, E, F, G, H, I]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map9[A, B, C, D, E, F, G, H, I]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map9 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -2761,18 +2779,19 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) Add(entity ecs.Entity, target ...e
 }
 
 // AddBatch adds the Map10's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map10's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map10[A, B, C, D, E, F, G, H, I, J]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map10 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -2845,18 +2864,19 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) Remove(entity ecs.Entity, target .
 }
 
 // RemoveBatch removes the Map10's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map10's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map10[A, B, C, D, E, F, G, H, I, J]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map10 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -3098,18 +3118,19 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) Add(entity ecs.Entity, target .
 }
 
 // AddBatch adds the Map11's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map11's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map11 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -3184,18 +3205,19 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) Remove(entity ecs.Entity, targe
 }
 
 // RemoveBatch removes the Map11's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map11's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map11 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 
@@ -3444,18 +3466,19 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) Add(entity ecs.Entity, targe
 }
 
 // AddBatch adds the Map12's components to many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map12's [ecs.Relation].
 //
 // See also [ecs.Batch.Add].
-func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) AddBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) AddBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map12 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, m.ids, nil, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, m.ids, nil)
+		return m.world.Batch().Exchange(filter, m.ids, nil)
 	}
 }
 
@@ -3532,18 +3555,19 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) Remove(entity ecs.Entity, ta
 }
 
 // RemoveBatch removes the Map12's components from many entities, matching a filter.
+// Returns the number of affected entities.
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map12's [ecs.Relation].
 //
 // See also [ecs.Batch.Remove].
-func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) {
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) RemoveBatch(filter ecs.Filter, target ...ecs.Entity) int {
 	if len(target) > 0 {
 		if !m.hasRelation {
 			panic("can't set target entity: Map12 has no relation")
 		}
-		m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
+		return m.world.Relations().ExchangeBatch(filter, nil, m.ids, m.relation, target[0])
 	} else {
-		m.world.Batch().Exchange(filter, nil, m.ids)
+		return m.world.Batch().Exchange(filter, nil, m.ids)
 	}
 }
 


Test patch:
diff --git a/ecs/batch_test.go b/ecs/batch_test.go
deleted file mode 100644
index e85b45a..0000000
--- a/ecs/batch_test.go
+++ /dev/null
@@ -1,1 +0,0 @@
-package ecs_test
diff --git a/ecs/relations_test.go b/ecs/relations_test.go
index 44756ce..3f0fdb0 100644
--- a/ecs/relations_test.go
+++ b/ecs/relations_test.go
@@ -39,14 +39,16 @@ func TestRelationsExchange(t *testing.T) {
 		_ = w.Relations().ExchangeBatchQ(filter, nil, nil, childID, parent2)
 	})
 
-	w.Relations().ExchangeBatch(filter, []ecs.ID{velID}, nil, childID, parent2)
+	cnt := w.Relations().ExchangeBatch(filter, []ecs.ID{velID}, nil, childID, parent2)
+	assert.Equal(t, 10, cnt)
 
 	relFilter = ecs.NewRelationFilter(ecs.All(posID, velID, childID), parent2)
 	query = w.Query(&relFilter)
 	assert.Equal(t, 10, query.Count())
 	query.Close()
 
-	w.Relations().ExchangeBatch(filter, nil, []ecs.ID{velID}, childID, parent1)
+	cnt = w.Relations().ExchangeBatch(filter, nil, []ecs.ID{velID}, childID, parent1)
+	assert.Equal(t, 10, cnt)
 
 	relFilter = ecs.NewRelationFilter(ecs.All(posID, childID), parent1)
 	query = w.Query(&relFilter)
diff --git a/ecs/world_test.go b/ecs/world_test.go
index fbeded7..d31ae7f 100644
--- a/ecs/world_test.go
+++ b/ecs/world_test.go
@@ -385,11 +385,14 @@ func TestWorldExchangeBatch(t *testing.T) {
 	assert.Equal(t, 100, query.Count())
 	query.Close()
 
-	w.Batch().Exchange(All(posID), nil, nil)
+	cnt := w.Batch().Exchange(All(posID), nil, nil)
+	assert.Equal(t, 0, cnt)
 
 	relFilter := NewRelationFilter(All(relID), target2)
-	w.Batch().Exchange(&relFilter, nil, []ID{relID})
-	w.Batch().Exchange(All(relID), nil, []ID{relID})
+	cnt = w.Batch().Exchange(&relFilter, nil, []ID{relID})
+	assert.Equal(t, 100, cnt)
+	cnt = w.Batch().Exchange(All(relID), nil, []ID{relID})
+	assert.Equal(t, 100, cnt)
 
 	assert.Equal(t, 702, len(events))
 	assert.Equal(t, EntityEvent{
@@ -402,7 +405,8 @@ func TestWorldExchangeBatch(t *testing.T) {
 		EventTypes:  event.ComponentRemoved | event.RelationChanged | event.TargetChanged,
 	}, events[701])
 
-	w.Batch().RemoveEntities(All(posID))
+	cnt = w.Batch().RemoveEntities(All(posID))
+	assert.Equal(t, 100, cnt)
 
 	assert.Equal(t, 802, len(events))
 
@@ -417,7 +421,8 @@ func TestWorldExchangeBatch(t *testing.T) {
 	assert.Equal(t, []ID{posID}, events[202].RemovedIDs)
 
 	filter = All(velID)
-	w.Batch().Remove(filter, velID)
+	cnt = w.Batch().Remove(filter, velID)
+	assert.Equal(t, 102, cnt)
 
 	filter = All(velID)
 	q := w.Query(filter)
@@ -425,7 +430,8 @@ func TestWorldExchangeBatch(t *testing.T) {
 	q.Close()
 
 	filter = All()
-	w.Batch().Add(filter, velID)
+	cnt = w.Batch().Add(filter, velID)
+	assert.Equal(t, 102, cnt)
 
 	w.Reset()