Repository: mlange-42/arche
Pull request #151 (2023-03-24 00:29:51)

Generic batch operations
Makes batch creation and removal of entities possible using generic mappers.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9eda4b9..641c192 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
 * Adds method `World.Reset()`, to allow for more efficient systematic simulations (!138)
 * Adds `World.Batch()` helper for performing optimized batch-creation and batch-removal of entities (!149)
 * Adds method `Mask.Exact()` to create a filter matching an exact component composition (!149)
+* Generic mappers (`Map1`, ...) have methods `NewEntities`, `NewEntitiesWith` and `RemoveEntities` for batch operations (!151)
 
 ### Performance
 
diff --git a/ecs/world.go b/ecs/world.go
index 67c65f1..2bbd95b 100644
--- a/ecs/world.go
+++ b/ecs/world.go
@@ -503,8 +503,8 @@ func (w *World) Query(filter Filter) Query {
 //
 // It provides the functionality to create and remove large numbers of entities in batches,
 // in a more efficient way.
-func (w *World) Batch() Batch {
-	return Batch{w}
+func (w *World) Batch() *Batch {
+	return &Batch{w}
 }
 
 // lock the world and get the lock bit for later unlocking.
diff --git a/generic/generate/main.go b/generic/generate/main.go
index 1edaa35..7669576 100644
--- a/generic/generate/main.go
+++ b/generic/generate/main.go
@@ -25,6 +25,7 @@ type query struct {
 	Arguments   string
 	IDTypes     string
 	IDAssign    string
+	IDAssign2   string
 	IDList      string
 }
 
@@ -62,6 +63,7 @@ func generateMaps() {
 		arguments := ""
 		idTypes := ""
 		idAssign := ""
+		idAssign2 := ""
 		idList := ""
 
 		if i > 0 {
@@ -76,6 +78,7 @@ func generateMaps() {
 			returnAll += fmt.Sprintf("(*%s)(m.world.Get(entity, m.id%d))", typeLetters[j], j)
 			arguments += fmt.Sprintf("%s *%s", strings.ToLower(typeLetters[j]), typeLetters[j])
 			idAssign += fmt.Sprintf("	id%d: ecs.ComponentID[%s](w),\n", j, typeLetters[j])
+			idAssign2 += fmt.Sprintf("	id%d: m.id%d,\n", j, j)
 			if j < i-1 {
 				returnAll += ",\n"
 				arguments += ", "
@@ -94,6 +97,7 @@ func generateMaps() {
 			Arguments:   arguments,
 			IDTypes:     idTypes,
 			IDAssign:    idAssign,
+			IDAssign2:   idAssign2,
 			IDList:      idList,
 		}
 		err = maps.Execute(&text, data)
diff --git a/generic/generate/map.go.txt b/generic/generate/map.go.txt
index be1fd4d..c093c2a 100644
--- a/generic/generate/map.go.txt
+++ b/generic/generate/map.go.txt
@@ -4,15 +4,18 @@
 // Map{{ .Index }} is a helper for mapping {{ .NumberStr }} components.
 type Map{{ .Index }}{{ .TypesFull }} struct {
 	world *ecs.World
+	mask ecs.Mask
 	{{ .IDTypes }}
 }
 
 // NewMap{{ .Index }} creates a new Map{{ .Index }} object.
 func NewMap{{ .Index }}{{ .TypesFull }}(w *ecs.World) Map{{ .Index }}{{ .Types }} {
-	return Map{{ .Index }}{{ .Types }}{
+	m := Map{{ .Index }}{{ .Types }}{
 		world: w,
 		{{ .IDAssign }}
 	}
+	m.mask = ecs.All({{ .IDList }})
+	return m
 }
 
 // Get all the Map{{ .Index }}'s components for the given entity.
@@ -30,6 +33,17 @@ func (m *Map{{ .Index }}{{ .Types }}) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map{{ .Index }}'s components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map{{ .Index }}{{ .Types }}) NewEntities(count int) Query{{ .Index }}{{ .Types }} {
+	query := m.world.Batch().NewEntities(count, {{ .IDList }})
+	return Query{{ .Index }}{{ .Types }}{
+		Query: query,
+		{{ .IDAssign2 }}
+	}
+}
+
 {{if .ReturnAll}}
 // NewEntityWith creates a new [ecs.Entity] with the Map{{ .Index }}'s components, using the supplied values.
 //
@@ -41,6 +55,17 @@ func (m *Map{{ .Index }}{{ .Types }}) NewEntityWith({{ .Arguments }}) ecs.Entity
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map{{ .Index }}'s components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map{{ .Index }}{{ .Types }}) NewEntitiesWith(count int, {{ .Arguments }}) Query{{ .Index }}{{ .Types }} {
+	query := m.world.Batch().NewEntitiesWith(count, {{ .Components }})
+	return Query{{ .Index }}{{ .Types }}{
+		Query: query,
+		{{ .IDAssign2 }}
+	}
+}
+
 // Add the Map{{ .Index }}'s components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -63,4 +88,17 @@ func (m *Map{{ .Index }}{{ .Types }}) Assign(entity ecs.Entity, {{ .Arguments }}
 // See also [ecs.World.Remove].
 func (m *Map{{ .Index }}{{ .Types }}) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, {{ .IDList }})
+}
+
+
+// RemoveEntities removes all components from the world that match the Map{{ .Index }}'s components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map{{ .Index }}{{ .Types }}) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
 }
\ No newline at end of file
diff --git a/generic/map_generated.go b/generic/map_generated.go
index ae97050..da8a4e9 100644
--- a/generic/map_generated.go
+++ b/generic/map_generated.go
@@ -11,15 +11,18 @@ import (
 // Map1 is a helper for mapping one components.
 type Map1[A any] struct {
 	world *ecs.World
+	mask  ecs.Mask
 	id0   ecs.ID
 }
 
 // NewMap1 creates a new Map1 object.
 func NewMap1[A any](w *ecs.World) Map1[A] {
-	return Map1[A]{
+	m := Map1[A]{
 		world: w,
 		id0:   ecs.ComponentID[A](w),
 	}
+	m.mask = ecs.All(m.id0)
+	return m
 }
 
 // Get all the Map1's components for the given entity.
@@ -37,6 +40,17 @@ func (m *Map1[A]) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map1's components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map1[A]) NewEntities(count int) Query1[A] {
+	query := m.world.Batch().NewEntities(count, m.id0)
+	return Query1[A]{
+		Query: query,
+		id0:   m.id0,
+	}
+}
+
 // NewEntityWith creates a new [ecs.Entity] with the Map1's components, using the supplied values.
 //
 // See also [ecs.World.NewEntityWith].
@@ -47,6 +61,17 @@ func (m *Map1[A]) NewEntityWith(a *A) ecs.Entity {
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map1's components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map1[A]) NewEntitiesWith(count int, a *A) Query1[A] {
+	query := m.world.Batch().NewEntitiesWith(count, ecs.Component{ID: m.id0, Comp: a})
+	return Query1[A]{
+		Query: query,
+		id0:   m.id0,
+	}
+}
+
 // Add the Map1's components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -70,22 +95,39 @@ func (m *Map1[A]) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, m.id0)
 }
 
+// RemoveEntities removes all components from the world that match the Map1's components.
+//
+// The argument determines whether entities are matched exactly, or if they may contain further components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map1[A]) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
+}
+
 //////////////////////////////////////////////////////////////////////////
 
 // Map2 is a helper for mapping two components.
 type Map2[A any, B any] struct {
 	world *ecs.World
+	mask  ecs.Mask
 	id0   ecs.ID
 	id1   ecs.ID
 }
 
 // NewMap2 creates a new Map2 object.
 func NewMap2[A any, B any](w *ecs.World) Map2[A, B] {
-	return Map2[A, B]{
+	m := Map2[A, B]{
 		world: w,
 		id0:   ecs.ComponentID[A](w),
 		id1:   ecs.ComponentID[B](w),
 	}
+	m.mask = ecs.All(m.id0, m.id1)
+	return m
 }
 
 // Get all the Map2's components for the given entity.
@@ -104,6 +146,18 @@ func (m *Map2[A, B]) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map2's components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map2[A, B]) NewEntities(count int) Query2[A, B] {
+	query := m.world.Batch().NewEntities(count, m.id0, m.id1)
+	return Query2[A, B]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+	}
+}
+
 // NewEntityWith creates a new [ecs.Entity] with the Map2's components, using the supplied values.
 //
 // See also [ecs.World.NewEntityWith].
@@ -115,6 +169,20 @@ func (m *Map2[A, B]) NewEntityWith(a *A, b *B) ecs.Entity {
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map2's components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map2[A, B]) NewEntitiesWith(count int, a *A, b *B) Query2[A, B] {
+	query := m.world.Batch().NewEntitiesWith(count, ecs.Component{ID: m.id0, Comp: a},
+		ecs.Component{ID: m.id1, Comp: b},
+	)
+	return Query2[A, B]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+	}
+}
+
 // Add the Map2's components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -139,11 +207,24 @@ func (m *Map2[A, B]) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, m.id0, m.id1)
 }
 
+// RemoveEntities removes all components from the world that match the Map2's components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map2[A, B]) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
+}
+
 //////////////////////////////////////////////////////////////////////////
 
 // Map3 is a helper for mapping three components.
 type Map3[A any, B any, C any] struct {
 	world *ecs.World
+	mask  ecs.Mask
 	id0   ecs.ID
 	id1   ecs.ID
 	id2   ecs.ID
@@ -151,12 +232,14 @@ type Map3[A any, B any, C any] struct {
 
 // NewMap3 creates a new Map3 object.
 func NewMap3[A any, B any, C any](w *ecs.World) Map3[A, B, C] {
-	return Map3[A, B, C]{
+	m := Map3[A, B, C]{
 		world: w,
 		id0:   ecs.ComponentID[A](w),
 		id1:   ecs.ComponentID[B](w),
 		id2:   ecs.ComponentID[C](w),
 	}
+	m.mask = ecs.All(m.id0, m.id1, m.id2)
+	return m
 }
 
 // Get all the Map3's components for the given entity.
@@ -176,6 +259,19 @@ func (m *Map3[A, B, C]) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map3's components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map3[A, B, C]) NewEntities(count int) Query3[A, B, C] {
+	query := m.world.Batch().NewEntities(count, m.id0, m.id1, m.id2)
+	return Query3[A, B, C]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+	}
+}
+
 // NewEntityWith creates a new [ecs.Entity] with the Map3's components, using the supplied values.
 //
 // See also [ecs.World.NewEntityWith].
@@ -188,6 +284,22 @@ func (m *Map3[A, B, C]) NewEntityWith(a *A, b *B, c *C) ecs.Entity {
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map3's components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map3[A, B, C]) NewEntitiesWith(count int, a *A, b *B, c *C) Query3[A, B, C] {
+	query := m.world.Batch().NewEntitiesWith(count, ecs.Component{ID: m.id0, Comp: a},
+		ecs.Component{ID: m.id1, Comp: b},
+		ecs.Component{ID: m.id2, Comp: c},
+	)
+	return Query3[A, B, C]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+	}
+}
+
 // Add the Map3's components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -213,11 +325,24 @@ func (m *Map3[A, B, C]) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, m.id0, m.id1, m.id2)
 }
 
+// RemoveEntities removes all components from the world that match the Map3's components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map3[A, B, C]) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
+}
+
 //////////////////////////////////////////////////////////////////////////
 
 // Map4 is a helper for mapping four components.
 type Map4[A any, B any, C any, D any] struct {
 	world *ecs.World
+	mask  ecs.Mask
 	id0   ecs.ID
 	id1   ecs.ID
 	id2   ecs.ID
@@ -226,13 +351,15 @@ type Map4[A any, B any, C any, D any] struct {
 
 // NewMap4 creates a new Map4 object.
 func NewMap4[A any, B any, C any, D any](w *ecs.World) Map4[A, B, C, D] {
-	return Map4[A, B, C, D]{
+	m := Map4[A, B, C, D]{
 		world: w,
 		id0:   ecs.ComponentID[A](w),
 		id1:   ecs.ComponentID[B](w),
 		id2:   ecs.ComponentID[C](w),
 		id3:   ecs.ComponentID[D](w),
 	}
+	m.mask = ecs.All(m.id0, m.id1, m.id2, m.id3)
+	return m
 }
 
 // Get all the Map4's components for the given entity.
@@ -253,6 +380,20 @@ func (m *Map4[A, B, C, D]) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map4's components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map4[A, B, C, D]) NewEntities(count int) Query4[A, B, C, D] {
+	query := m.world.Batch().NewEntities(count, m.id0, m.id1, m.id2, m.id3)
+	return Query4[A, B, C, D]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+	}
+}
+
 // NewEntityWith creates a new [ecs.Entity] with the Map4's components, using the supplied values.
 //
 // See also [ecs.World.NewEntityWith].
@@ -266,6 +407,24 @@ func (m *Map4[A, B, C, D]) NewEntityWith(a *A, b *B, c *C, d *D) ecs.Entity {
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map4's components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map4[A, B, C, D]) NewEntitiesWith(count int, a *A, b *B, c *C, d *D) Query4[A, B, C, D] {
+	query := m.world.Batch().NewEntitiesWith(count, ecs.Component{ID: m.id0, Comp: a},
+		ecs.Component{ID: m.id1, Comp: b},
+		ecs.Component{ID: m.id2, Comp: c},
+		ecs.Component{ID: m.id3, Comp: d},
+	)
+	return Query4[A, B, C, D]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+	}
+}
+
 // Add the Map4's components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -292,11 +451,24 @@ func (m *Map4[A, B, C, D]) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, m.id0, m.id1, m.id2, m.id3)
 }
 
+// RemoveEntities removes all components from the world that match the Map4's components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map4[A, B, C, D]) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
+}
+
 //////////////////////////////////////////////////////////////////////////
 
 // Map5 is a helper for mapping five components.
 type Map5[A any, B any, C any, D any, E any] struct {
 	world *ecs.World
+	mask  ecs.Mask
 	id0   ecs.ID
 	id1   ecs.ID
 	id2   ecs.ID
@@ -306,7 +478,7 @@ type Map5[A any, B any, C any, D any, E any] struct {
 
 // NewMap5 creates a new Map5 object.
 func NewMap5[A any, B any, C any, D any, E any](w *ecs.World) Map5[A, B, C, D, E] {
-	return Map5[A, B, C, D, E]{
+	m := Map5[A, B, C, D, E]{
 		world: w,
 		id0:   ecs.ComponentID[A](w),
 		id1:   ecs.ComponentID[B](w),
@@ -314,6 +486,8 @@ func NewMap5[A any, B any, C any, D any, E any](w *ecs.World) Map5[A, B, C, D, E
 		id3:   ecs.ComponentID[D](w),
 		id4:   ecs.ComponentID[E](w),
 	}
+	m.mask = ecs.All(m.id0, m.id1, m.id2, m.id3, m.id4)
+	return m
 }
 
 // Get all the Map5's components for the given entity.
@@ -335,6 +509,21 @@ func (m *Map5[A, B, C, D, E]) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map5's components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map5[A, B, C, D, E]) NewEntities(count int) Query5[A, B, C, D, E] {
+	query := m.world.Batch().NewEntities(count, m.id0, m.id1, m.id2, m.id3, m.id4)
+	return Query5[A, B, C, D, E]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+	}
+}
+
 // NewEntityWith creates a new [ecs.Entity] with the Map5's components, using the supplied values.
 //
 // See also [ecs.World.NewEntityWith].
@@ -349,6 +538,26 @@ func (m *Map5[A, B, C, D, E]) NewEntityWith(a *A, b *B, c *C, d *D, e *E) ecs.En
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map5's components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map5[A, B, C, D, E]) NewEntitiesWith(count int, a *A, b *B, c *C, d *D, e *E) Query5[A, B, C, D, E] {
+	query := m.world.Batch().NewEntitiesWith(count, ecs.Component{ID: m.id0, Comp: a},
+		ecs.Component{ID: m.id1, Comp: b},
+		ecs.Component{ID: m.id2, Comp: c},
+		ecs.Component{ID: m.id3, Comp: d},
+		ecs.Component{ID: m.id4, Comp: e},
+	)
+	return Query5[A, B, C, D, E]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+	}
+}
+
 // Add the Map5's components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -376,11 +585,24 @@ func (m *Map5[A, B, C, D, E]) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, m.id0, m.id1, m.id2, m.id3, m.id4)
 }
 
+// RemoveEntities removes all components from the world that match the Map5's components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map5[A, B, C, D, E]) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
+}
+
 //////////////////////////////////////////////////////////////////////////
 
 // Map6 is a helper for mapping six components.
 type Map6[A any, B any, C any, D any, E any, F any] struct {
 	world *ecs.World
+	mask  ecs.Mask
 	id0   ecs.ID
 	id1   ecs.ID
 	id2   ecs.ID
@@ -391,7 +613,7 @@ type Map6[A any, B any, C any, D any, E any, F any] struct {
 
 // NewMap6 creates a new Map6 object.
 func NewMap6[A any, B any, C any, D any, E any, F any](w *ecs.World) Map6[A, B, C, D, E, F] {
-	return Map6[A, B, C, D, E, F]{
+	m := Map6[A, B, C, D, E, F]{
 		world: w,
 		id0:   ecs.ComponentID[A](w),
 		id1:   ecs.ComponentID[B](w),
@@ -400,6 +622,8 @@ func NewMap6[A any, B any, C any, D any, E any, F any](w *ecs.World) Map6[A, B,
 		id4:   ecs.ComponentID[E](w),
 		id5:   ecs.ComponentID[F](w),
 	}
+	m.mask = ecs.All(m.id0, m.id1, m.id2, m.id3, m.id4, m.id5)
+	return m
 }
 
 // Get all the Map6's components for the given entity.
@@ -422,6 +646,22 @@ func (m *Map6[A, B, C, D, E, F]) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map6's components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map6[A, B, C, D, E, F]) NewEntities(count int) Query6[A, B, C, D, E, F] {
+	query := m.world.Batch().NewEntities(count, m.id0, m.id1, m.id2, m.id3, m.id4, m.id5)
+	return Query6[A, B, C, D, E, F]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+	}
+}
+
 // NewEntityWith creates a new [ecs.Entity] with the Map6's components, using the supplied values.
 //
 // See also [ecs.World.NewEntityWith].
@@ -437,6 +677,28 @@ func (m *Map6[A, B, C, D, E, F]) NewEntityWith(a *A, b *B, c *C, d *D, e *E, f *
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map6's components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map6[A, B, C, D, E, F]) NewEntitiesWith(count int, a *A, b *B, c *C, d *D, e *E, f *F) Query6[A, B, C, D, E, F] {
+	query := m.world.Batch().NewEntitiesWith(count, ecs.Component{ID: m.id0, Comp: a},
+		ecs.Component{ID: m.id1, Comp: b},
+		ecs.Component{ID: m.id2, Comp: c},
+		ecs.Component{ID: m.id3, Comp: d},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+	)
+	return Query6[A, B, C, D, E, F]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+	}
+}
+
 // Add the Map6's components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -465,11 +727,24 @@ func (m *Map6[A, B, C, D, E, F]) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, m.id0, m.id1, m.id2, m.id3, m.id4, m.id5)
 }
 
+// RemoveEntities removes all components from the world that match the Map6's components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map6[A, B, C, D, E, F]) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
+}
+
 //////////////////////////////////////////////////////////////////////////
 
 // Map7 is a helper for mapping seven components.
 type Map7[A any, B any, C any, D any, E any, F any, G any] struct {
 	world *ecs.World
+	mask  ecs.Mask
 	id0   ecs.ID
 	id1   ecs.ID
 	id2   ecs.ID
@@ -481,7 +756,7 @@ type Map7[A any, B any, C any, D any, E any, F any, G any] struct {
 
 // NewMap7 creates a new Map7 object.
 func NewMap7[A any, B any, C any, D any, E any, F any, G any](w *ecs.World) Map7[A, B, C, D, E, F, G] {
-	return Map7[A, B, C, D, E, F, G]{
+	m := Map7[A, B, C, D, E, F, G]{
 		world: w,
 		id0:   ecs.ComponentID[A](w),
 		id1:   ecs.ComponentID[B](w),
@@ -491,6 +766,8 @@ func NewMap7[A any, B any, C any, D any, E any, F any, G any](w *ecs.World) Map7
 		id5:   ecs.ComponentID[F](w),
 		id6:   ecs.ComponentID[G](w),
 	}
+	m.mask = ecs.All(m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6)
+	return m
 }
 
 // Get all the Map7's components for the given entity.
@@ -514,6 +791,23 @@ func (m *Map7[A, B, C, D, E, F, G]) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map7's components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map7[A, B, C, D, E, F, G]) NewEntities(count int) Query7[A, B, C, D, E, F, G] {
+	query := m.world.Batch().NewEntities(count, m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6)
+	return Query7[A, B, C, D, E, F, G]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+		id6:   m.id6,
+	}
+}
+
 // NewEntityWith creates a new [ecs.Entity] with the Map7's components, using the supplied values.
 //
 // See also [ecs.World.NewEntityWith].
@@ -530,6 +824,30 @@ func (m *Map7[A, B, C, D, E, F, G]) NewEntityWith(a *A, b *B, c *C, d *D, e *E,
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map7's components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map7[A, B, C, D, E, F, G]) NewEntitiesWith(count int, a *A, b *B, c *C, d *D, e *E, f *F, g *G) Query7[A, B, C, D, E, F, G] {
+	query := m.world.Batch().NewEntitiesWith(count, ecs.Component{ID: m.id0, Comp: a},
+		ecs.Component{ID: m.id1, Comp: b},
+		ecs.Component{ID: m.id2, Comp: c},
+		ecs.Component{ID: m.id3, Comp: d},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+	)
+	return Query7[A, B, C, D, E, F, G]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+		id6:   m.id6,
+	}
+}
+
 // Add the Map7's components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -559,11 +877,24 @@ func (m *Map7[A, B, C, D, E, F, G]) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6)
 }
 
+// RemoveEntities removes all components from the world that match the Map7's components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map7[A, B, C, D, E, F, G]) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
+}
+
 //////////////////////////////////////////////////////////////////////////
 
 // Map8 is a helper for mapping eight components.
 type Map8[A any, B any, C any, D any, E any, F any, G any, H any] struct {
 	world *ecs.World
+	mask  ecs.Mask
 	id0   ecs.ID
 	id1   ecs.ID
 	id2   ecs.ID
@@ -576,7 +907,7 @@ type Map8[A any, B any, C any, D any, E any, F any, G any, H any] struct {
 
 // NewMap8 creates a new Map8 object.
 func NewMap8[A any, B any, C any, D any, E any, F any, G any, H any](w *ecs.World) Map8[A, B, C, D, E, F, G, H] {
-	return Map8[A, B, C, D, E, F, G, H]{
+	m := Map8[A, B, C, D, E, F, G, H]{
 		world: w,
 		id0:   ecs.ComponentID[A](w),
 		id1:   ecs.ComponentID[B](w),
@@ -587,6 +918,8 @@ func NewMap8[A any, B any, C any, D any, E any, F any, G any, H any](w *ecs.Worl
 		id6:   ecs.ComponentID[G](w),
 		id7:   ecs.ComponentID[H](w),
 	}
+	m.mask = ecs.All(m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6, m.id7)
+	return m
 }
 
 // Get all the Map8's components for the given entity.
@@ -611,6 +944,24 @@ func (m *Map8[A, B, C, D, E, F, G, H]) NewEntity() ecs.Entity {
 	return entity
 }
 
+// NewEntities creates entities with the Map8's components.
+//
+// See also [ecs.Batch.NewEntities].
+func (m *Map8[A, B, C, D, E, F, G, H]) NewEntities(count int) Query8[A, B, C, D, E, F, G, H] {
+	query := m.world.Batch().NewEntities(count, m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6, m.id7)
+	return Query8[A, B, C, D, E, F, G, H]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+		id6:   m.id6,
+		id7:   m.id7,
+	}
+}
+
 // NewEntityWith creates a new [ecs.Entity] with the Map8's components, using the supplied values.
 //
 // See also [ecs.World.NewEntityWith].
@@ -628,6 +979,32 @@ func (m *Map8[A, B, C, D, E, F, G, H]) NewEntityWith(a *A, b *B, c *C, d *D, e *
 	return entity
 }
 
+// NewEntitiesWith creates entities with the Map8's components, using the supplied values.
+//
+// See also [ecs.Batch.NewEntitiesWith].
+func (m *Map8[A, B, C, D, E, F, G, H]) NewEntitiesWith(count int, a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H) Query8[A, B, C, D, E, F, G, H] {
+	query := m.world.Batch().NewEntitiesWith(count, ecs.Component{ID: m.id0, Comp: a},
+		ecs.Component{ID: m.id1, Comp: b},
+		ecs.Component{ID: m.id2, Comp: c},
+		ecs.Component{ID: m.id3, Comp: d},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+		ecs.Component{ID: m.id7, Comp: h},
+	)
+	return Query8[A, B, C, D, E, F, G, H]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+		id6:   m.id6,
+		id7:   m.id7,
+	}
+}
+
 // Add the Map8's components to the given entity.
 //
 // See also [ecs.World.Add].
@@ -657,3 +1034,15 @@ func (m *Map8[A, B, C, D, E, F, G, H]) Assign(entity ecs.Entity, a *A, b *B, c *
 func (m *Map8[A, B, C, D, E, F, G, H]) Remove(entity ecs.Entity) {
 	m.world.Remove(entity, m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6, m.id7)
 }
+
+// RemoveEntities removes all components from the world that match the Map8's components.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map8[A, B, C, D, E, F, G, H]) RemoveEntities(exact bool) {
+	if exact {
+		filter := m.mask.Exact()
+		m.world.Batch().RemoveEntities(&filter)
+	} else {
+		m.world.Batch().RemoveEntities(m.mask)
+	}
+}


Test patch:
diff --git a/generic/map_generated_test.go b/generic/map_generated_test.go
index 096387c..05ce85e 100644
--- a/generic/map_generated_test.go
+++ b/generic/map_generated_test.go
@@ -34,6 +34,13 @@ func TestMap1Generated(t *testing.T) {
 
 	s0 = mut.Get(e)
 	assert.NotNil(t, s0)
+
+	q := mut.NewEntities(2)
+	q.Close()
+	q = mut.NewEntitiesWith(2, &testStruct0{})
+	q.Close()
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
 }
 
 func TestMap2Generated(t *testing.T) {
@@ -70,6 +77,15 @@ func TestMap2Generated(t *testing.T) {
 	s0, s1 = mut.Get(e)
 	assert.NotNil(t, s0)
 	assert.NotNil(t, s1)
+
+	q := mut.NewEntities(2)
+	q.Close()
+	q = mut.NewEntitiesWith(2,
+		&testStruct0{}, &testStruct1{},
+	)
+	q.Close()
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
 }
 
 func TestMap3Generated(t *testing.T) {
@@ -112,6 +128,15 @@ func TestMap3Generated(t *testing.T) {
 	s0, s1, _ = mut.Get(e)
 	assert.NotNil(t, s0)
 	assert.NotNil(t, s1)
+
+	q := mut.NewEntities(2)
+	q.Close()
+	q = mut.NewEntitiesWith(2,
+		&testStruct0{}, &testStruct1{}, &testStruct2{},
+	)
+	q.Close()
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
 }
 
 func TestMap4Generated(t *testing.T) {
@@ -154,6 +179,15 @@ func TestMap4Generated(t *testing.T) {
 	s0, s1, _, _ = mut.Get(e)
 	assert.NotNil(t, s0)
 	assert.NotNil(t, s1)
+
+	q := mut.NewEntities(2)
+	q.Close()
+	q = mut.NewEntitiesWith(2,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+	)
+	q.Close()
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
 }
 
 func TestMap5Generated(t *testing.T) {
@@ -199,6 +233,16 @@ func TestMap5Generated(t *testing.T) {
 	s0, s1, _, _, _ = mut.Get(e)
 	assert.NotNil(t, s0)
 	assert.NotNil(t, s1)
+
+	q := mut.NewEntities(2)
+	q.Close()
+	q = mut.NewEntitiesWith(2,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{},
+	)
+	q.Close()
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
 }
 
 func TestMap6Generated(t *testing.T) {
@@ -244,6 +288,16 @@ func TestMap6Generated(t *testing.T) {
 	s0, s1, _, _, _, _ = mut.Get(e)
 	assert.NotNil(t, s0)
 	assert.NotNil(t, s1)
+
+	q := mut.NewEntities(2)
+	q.Close()
+	q = mut.NewEntitiesWith(2,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{},
+	)
+	q.Close()
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
 }
 
 func TestMap7Generated(t *testing.T) {
@@ -289,6 +343,16 @@ func TestMap7Generated(t *testing.T) {
 	s0, s1, _, _, _, _, _ = mut.Get(e)
 	assert.NotNil(t, s0)
 	assert.NotNil(t, s1)
+
+	q := mut.NewEntities(2)
+	q.Close()
+	q = mut.NewEntitiesWith(2,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{},
+	)
+	q.Close()
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
 }
 
 func TestMap8Generated(t *testing.T) {
@@ -334,4 +398,14 @@ func TestMap8Generated(t *testing.T) {
 	s0, s1, _, _, _, _, _, _ = mut.Get(e)
 	assert.NotNil(t, s0)
 	assert.NotNil(t, s1)
+
+	q := mut.NewEntities(2)
+	q.Close()
+	q = mut.NewEntitiesWith(2,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+	)
+	q.Close()
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
 }