Repository: mlange-42/arche
Pull request #468 (2025-01-08 12:21:51)

Add `Batch.New` and `Batch.NewQ`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b01b5aa..9402c35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@
 
 * Removes `ecs.Config`; the world is configured with initial capacities directly (#467)
 
+### Features
+
+* Adds methods `Batch.New` and `Batch.NewQ` for batch entity creation (#468)
+
 ### Performance
 
 * Optimizes entity creation by an altered growth policy for archetypes, entity list and entity pool (#464, #466)
diff --git a/_examples/batch_ops/main.go b/_examples/batch_ops/main.go
index c08e1b2..2eb43b3 100644
--- a/_examples/batch_ops/main.go
+++ b/_examples/batch_ops/main.go
@@ -36,14 +36,11 @@ func run() {
 	posID := ecs.ComponentID[Position](&world)
 	velID := ecs.ComponentID[Velocity](&world)
 
-	// Create an entity builder with components.
-	builder := ecs.NewBuilder(&world, posID, velID)
-
 	// Batch-create entities.
-	builder.NewBatch(100)
+	world.Batch().New(100, posID, velID)
 
 	// Batch-create entities, and iterate them.
-	query := builder.NewBatchQ(100)
+	query := world.Batch().NewQ(100, posID, velID)
 	for query.Next() {
 		pos := (*Position)(query.Get(posID))
 		pos.X = 1.0
diff --git a/docs/content/guide/batch-ops/index.md b/docs/content/guide/batch-ops/index.md
index 019e2cf..0d99344 100644
--- a/docs/content/guide/batch-ops/index.md
+++ b/docs/content/guide/batch-ops/index.md
@@ -17,7 +17,7 @@ Most batch methods come in two flavors. A "normal" one, and one suffixed with `Q
 
 Entity creation is probably the most common use case for batching.
 When the number of similar entities that are to be created are known,
-creation can be batched with {{< api ecs Builder.NewBatch >}}.
+creation can be batched with {{< api ecs Batch.New >}}.
 In the generic API, *MapX* provide e.g. {{< api generic Map2.NewBatch >}}.
 
 {{< tabs >}}
diff --git a/ecs/batch.go b/ecs/batch.go
index 15fc7ce..3ff27b6 100644
--- a/ecs/batch.go
+++ b/ecs/batch.go
@@ -7,6 +7,20 @@ type Batch struct {
 	world *World
 }
 
+// New creates many entities with the given components.
+//
+// See also [Builder] for more flexible batch-creation of entities.
+func (b *Batch) New(count int, comps ...ID) {
+	b.world.newEntities(count, ID{}, false, Entity{}, comps...)
+}
+
+// NewQ creates many entities with the given components, and returns a query over them.
+//
+// See also [Builder] for more flexible batch-creation of entities.
+func (b *Batch) NewQ(count int, comps ...ID) Query {
+	return b.world.newEntitiesQuery(count, ID{}, false, Entity{}, comps...)
+}
+
 // Add adds components to many entities, matching a filter.
 // Returns the number of affected entities.
 //
diff --git a/ecs/doc.go b/ecs/doc.go
index c5de1fa..b047ae4 100644
--- a/ecs/doc.go
+++ b/ecs/doc.go
@@ -46,7 +46,7 @@
 //   - Exchange components: [Relations.Exchange]
 //
 // Batch-manipulations of many entities:
-//   - Create entities: [Builder.NewBatch], [Builder.NewBatchQ]
+//   - Create entities: [Builder.NewBatch], [Builder.NewBatchQ], [Batch.New], [Batch.NewQ]
 //   - Remove entities: [Batch.RemoveEntities]
 //   - Add components: [Batch.Add], [Batch.AddQ]
 //   - Remove components: [Batch.Remove], [Batch.RemoveQ]
diff --git a/generic/_generate/map.go.txt b/generic/_generate/map.go.txt
index b1b7b84..3a3eca8 100644
--- a/generic/_generate/map.go.txt
+++ b/generic/_generate/map.go.txt
@@ -102,8 +102,6 @@ func (m *Map{{ .Index }}{{ .Types }}) NewBatchQ(count int, target ...ecs.Entity)
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map{{ .Index }}'s [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map{{ .Index }}{{ .Types }}) NewWith({{ .Arguments }}, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
diff --git a/generic/map_generated.go b/generic/map_generated.go
index 22d1ecd..124af52 100644
--- a/generic/map_generated.go
+++ b/generic/map_generated.go
@@ -109,8 +109,6 @@ func (m *Map1[A]) NewBatchQ(count int, target ...ecs.Entity) Query1[A] {
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map1's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map1[A]) NewWith(a *A, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -386,8 +384,6 @@ func (m *Map2[A, B]) NewBatchQ(count int, target ...ecs.Entity) Query2[A, B] {
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map2's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map2[A, B]) NewWith(a *A, b *B, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -672,8 +668,6 @@ func (m *Map3[A, B, C]) NewBatchQ(count int, target ...ecs.Entity) Query3[A, B,
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map3's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map3[A, B, C]) NewWith(a *A, b *B, c *C, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -967,8 +961,6 @@ func (m *Map4[A, B, C, D]) NewBatchQ(count int, target ...ecs.Entity) Query4[A,
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map4's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map4[A, B, C, D]) NewWith(a *A, b *B, c *C, d *D, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -1271,8 +1263,6 @@ func (m *Map5[A, B, C, D, E]) NewBatchQ(count int, target ...ecs.Entity) Query5[
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map5's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map5[A, B, C, D, E]) NewWith(a *A, b *B, c *C, d *D, e *E, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -1584,8 +1574,6 @@ func (m *Map6[A, B, C, D, E, F]) NewBatchQ(count int, target ...ecs.Entity) Quer
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map6's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map6[A, B, C, D, E, F]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -1906,8 +1894,6 @@ func (m *Map7[A, B, C, D, E, F, G]) NewBatchQ(count int, target ...ecs.Entity) Q
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map7's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map7[A, B, C, D, E, F, G]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -2237,8 +2223,6 @@ func (m *Map8[A, B, C, D, E, F, G, H]) NewBatchQ(count int, target ...ecs.Entity
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map8's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map8[A, B, C, D, E, F, G, H]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -2577,8 +2561,6 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) NewBatchQ(count int, target ...ecs.Ent
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map9's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map9[A, B, C, D, E, F, G, H, I]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -2926,8 +2908,6 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewBatchQ(count int, target ...ecs
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map10's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -3284,8 +3264,6 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewBatchQ(count int, target ...
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map11's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J, k *K, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(
@@ -3651,8 +3629,6 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewBatchQ(count int, target
 //
 // The optional argument can be used to set the target [ecs.Entity] for the Map12's [ecs.Relation].
 // A potential [ecs.Listener] is notified about the relation target change in a separate event.
-//
-// See also [ecs.NewBuilderWith].
 func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J, k *K, l *L, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		entity := m.world.NewEntityFn(


Test patch:
diff --git a/docs/content/guide/batch-ops/batch_test.go b/docs/content/guide/batch-ops/batch_test.go
index 84f39b4..17c6ad4 100644
--- a/docs/content/guide/batch-ops/batch_test.go
+++ b/docs/content/guide/batch-ops/batch_test.go
@@ -30,9 +30,7 @@ func TestBatchCreate(t *testing.T) {
 	posID := ecs.ComponentID[Position](&world)
 	headID := ecs.ComponentID[Heading](&world)
 
-	builder := ecs.NewBuilder(&world, posID, headID)
-
-	builder.NewBatch(100)
+	world.Batch().New(100, posID, headID)
 }
 
 func TestBatchCreateGeneric(t *testing.T) {
@@ -49,9 +47,7 @@ func TestBatchCreateQuery(t *testing.T) {
 	posID := ecs.ComponentID[Position](&world)
 	headID := ecs.ComponentID[Heading](&world)
 
-	builder := ecs.NewBuilder(&world, posID, headID)
-
-	query := builder.NewBatchQ(100)
+	query := world.Batch().NewQ(100, posID, headID)
 	for query.Next() {
 		pos := (*Position)(query.Get(posID))
 		head := (*Heading)(query.Get(headID))
@@ -84,8 +80,7 @@ func TestBatchAddQuery(t *testing.T) {
 	headID := ecs.ComponentID[Heading](&world)
 
 	// Create 100 entities with Position.
-	builder := ecs.NewBuilder(&world, posID)
-	builder.NewBatch(100)
+	world.Batch().New(100, posID)
 
 	// Filter for entities with Position.
 	filter := ecs.All(posID)
@@ -124,8 +119,7 @@ func TestBatchRelations(t *testing.T) {
 	parent := world.NewEntity()
 
 	// Create 100 entities with ChildOf relation.
-	builder := ecs.NewBuilder(&world, childID)
-	builder.NewBatch(100)
+	world.Batch().New(100, childID)
 
 	// Filter for entities with ChildOf.
 	filter := ecs.All(childID)
@@ -158,8 +152,7 @@ func TestBatchRemoveEntities(t *testing.T) {
 	posID := ecs.ComponentID[Position](&world)
 
 	// Create 100 entities with Position.
-	builder := ecs.NewBuilder(&world, posID)
-	builder.NewBatch(100)
+	world.Batch().New(100, posID)
 
 	// Filter for entities with Position.
 	filter := ecs.All(posID)
diff --git a/ecs/batch_examples_test.go b/ecs/batch_examples_test.go
index 0e2070d..3b08c8c 100644
--- a/ecs/batch_examples_test.go
+++ b/ecs/batch_examples_test.go
@@ -16,6 +16,31 @@ func ExampleBatch() {
 	// Output:
 }
 
+func ExampleBatch_New() {
+	world := ecs.NewWorld()
+
+	posID := ecs.ComponentID[Position](&world)
+	velID := ecs.ComponentID[Velocity](&world)
+
+	world.Batch().New(100, posID, velID)
+	// Output:
+}
+
+func ExampleBatch_NewQ() {
+	world := ecs.NewWorld()
+
+	posID := ecs.ComponentID[Position](&world)
+	velID := ecs.ComponentID[Velocity](&world)
+
+	query := world.Batch().NewQ(100, posID, velID)
+
+	for query.Next() {
+		pos := (*Position)(query.Get(posID))
+		pos.X = 100
+	}
+	// Output:
+}
+
 func ExampleBatch_Add() {
 	world := ecs.NewWorld()