Repository: mlange-42/arche
Pull request #64 (2023-02-13 10:59:43)

Streamline queries
* Renamed structs `Q0Builder` etc. to `Filter0` etc.
* Renamed constructors `Query0` etc. to `NewFilter0` etc.
* Renamed `Q0` etc. to `Query0` etc.
* Renamed `Q0Builder.Build` etc. to `Filter0.Query` etc.
* Use Go `generate` to generate code for generic filters and queries


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1f1aa7..1abf2db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,7 @@
 
 ### Other
 
-* Overhaul of the module structure, with generics and filters in separate packages (#55, #57)
+* Overhaul of the module structure, with generics and filters in separate packages (#55, #57, #61, #64)
 * Generic queries are compiled to masks and cached on first build (#62)
 
 ## [[v0.3.1]](https://github.com/mlange-42/arche/compare/v0.3.0...v0.3.1)
diff --git a/README.md b/README.md
index d7cd33c..515e4c6 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,6 @@
 [Department for Ecological Modelling](https://www.ufz.de/index.php?en=34213) at the
 [Helmholtz Centre for Environmental Research](https://www.ufz.de).
 
-**:warning: Arche is still under rapid development. Be prepared for frequent API changes! :warning:**  
-
-
 ## Installations
 
 ```shell
@@ -81,18 +78,19 @@ func main() {
 		vel.Y = rand.NormFloat64()
 	}
 
-	// Generic queries support up to 8 components.
-	// For more components, use World.Query() directly.
-	query := generic.Query2[Position, Velocity]()
+	// Create a generic filter.
+	// Generic filter support up to 5 components.
+	// For more components, use World.Query()
+	filter := generic.NewFilter2[Position, Velocity]()
 
 	// Time loop.
 	for t := 0; t < 1000; t++ {
-		// Get a fresh query iterator.
-		q := query.Build(&world)
-		// Iterate it.
-		for q.Next() {
+		// Get a fresh query.
+		query := filter.Query(&world)
+		// Iterate it
+		for query.Next() {
 			// Component access through a Query.
-			pos, vel := q.GetAll()
+			pos, vel := query.GetAll()
 			// Update component fields.
 			pos.X += vel.X
 			pos.Y += vel.Y
@@ -147,13 +145,7 @@ Generic functions and types provide type-safety and are more user-friendly than
 Depending on the machine the code is running on, generics may or may not incur an overhead.
 The worst to expect is a doubling of iteration + access time (from 2.5ns/op to 4ns/op),
 while on other machines both approaches are equally fast.
-
 For performance-critical code, the use of the ID-based methods of `ecs.World` may be worth testing.
-Component IDs are retrieved like this:
-
-```go
-posID := ComponentID[Position](&world)
-```
 
 For more details, see the [API docs](https://pkg.go.dev/github.com/mlange-42/arche) and
 [examples](https://github.com/mlange-42/arche/tree/main/examples).
diff --git a/ecs/query.go b/ecs/query.go
index 7cfb793..98b43b8 100644
--- a/ecs/query.go
+++ b/ecs/query.go
@@ -51,8 +51,8 @@ func (f MaskPair) Matches(bits BitMask) bool {
 	return bits.Contains(f.Mask.BitMask) && (ex == 0 || !bits.ContainsAny(ex))
 }
 
-// EntityIter is the interface for iterable queries
-type EntityIter interface {
+// QueryIter is the interface for iterable queries
+type QueryIter interface {
 	// Next proceeds to the next [Entity] in the Query.
 	Next() bool
 	// Has returns whether the current [Entity] has the given component
diff --git a/examples/base/main.go b/examples/base/main.go
index 0fbd182..f795776 100644
--- a/examples/base/main.go
+++ b/examples/base/main.go
@@ -48,13 +48,13 @@ func main() {
 		vel.Y = rand.NormFloat64()
 	}
 
-	// Create a query
-	query := ecs.All(posID, velID).Without(rotID)
+	// Create a filter
+	filter := ecs.All(posID, velID).Without(rotID)
 
 	// Time loop.
 	for t := 0; t < 1000; t++ {
 		// Get a fresh query iterator.
-		q := world.Query(query)
+		q := world.Query(filter)
 		// Iterate it
 		for q.Next() {
 			// Component access through a Query.
diff --git a/examples/generic/main.go b/examples/generic/main.go
index d2abb37..caf67ab 100644
--- a/examples/generic/main.go
+++ b/examples/generic/main.go
@@ -50,12 +50,12 @@ func main() {
 
 	// Generic queries support up to 8 components.
 	// For more components, use World.Query() directly.
-	query := generic.Query2[Position, Velocity]()
+	filter := generic.NewFilter2[Position, Velocity]()
 
 	// Time loop.
 	for t := 0; t < 1000; t++ {
 		// Get a fresh query iterator.
-		q := query.Build(&world)
+		q := filter.Query(&world)
 		// Iterate it.
 		for q.Next() {
 			// Component access through a Query.
@@ -67,14 +67,14 @@ func main() {
 	}
 
 	// A more complex generic query using optional and excluded components:
-	query =
+	filter =
 		generic.
-			Query2[Position, Velocity]().        // Components provided through Get... methods
+			NewFilter2[Position, Velocity]().    // Components provided through Get... methods
 			Optional(generic.Mask1[Velocity]()). // but those may be nil
 			With(generic.Mask1[Elevation]()).    // additional required components
-			Without(generic.Mask1[Rotation]())   // and entities with any of these are excluded.
+			Without(generic.Mask1[Rotation]())   // entities with any of these are excluded.
 
-	q := query.Build(&world)
+	q := filter.Query(&world)
 
 	for q.Next() {
 		pos, vel := q.GetAll()
diff --git a/generic/compiled.go b/generic/compiled.go
new file mode 100644
index 0000000..810332e
--- /dev/null
+++ b/generic/compiled.go
@@ -0,0 +1,35 @@
+package generic
+
+import (
+	"reflect"
+
+	"github.com/mlange-42/arche/ecs"
+)
+
+type compiledQuery struct {
+	mask     ecs.Mask
+	exclude  ecs.Mask
+	Ids      []ecs.ID
+	compiled bool
+}
+
+func (q *compiledQuery) Compile(w *ecs.World, include, optional, exclude []reflect.Type) {
+	if q.compiled {
+		return
+	}
+	q.Ids = toIds(w, include)
+	q.mask = toMaskOptional(w, q.Ids, optional)
+	q.exclude = toMask(w, exclude)
+	q.compiled = true
+}
+
+func (q *compiledQuery) Filter() ecs.MaskPair {
+	return ecs.MaskPair{
+		Mask:    q.mask,
+		Exclude: q.exclude,
+	}
+}
+
+func (q *compiledQuery) Reset() {
+	q.compiled = false
+}
diff --git a/generic/generic.go b/generic/functions.go
similarity index 100%
rename from generic/generic.go
rename to generic/functions.go
diff --git a/generic/generic_test.go b/generic/functions_test.go
similarity index 100%
rename from generic/generic_test.go
rename to generic/functions_test.go
diff --git a/generic/generate.go b/generic/generate.go
new file mode 100644
index 0000000..1fcc44b
--- /dev/null
+++ b/generic/generate.go
@@ -0,0 +1,4 @@
+package generic
+
+//go:generate go run ./generate
+//go:generate go fmt
diff --git a/generic/generate/header.gotxt b/generic/generate/header.gotxt
new file mode 100644
index 0000000..d9fe156
--- /dev/null
+++ b/generic/generate/header.gotxt
@@ -0,0 +1,9 @@
+package generic
+
+// Code generated by go generate; DO NOT EDIT.
+
+import (
+	"reflect"
+
+	"github.com/mlange-42/arche/ecs"
+)
diff --git a/generic/generate/main.go b/generic/generate/main.go
new file mode 100644
index 0000000..0a93c09
--- /dev/null
+++ b/generic/generate/main.go
@@ -0,0 +1,112 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"text/template"
+)
+
+var typeLetters = []string{"A", "B", "C", "D", "E", "F", "G", "H"}
+
+type query struct {
+	Index       int
+	Types       string
+	TypesFull   string
+	TypesReturn string
+	ReturnAll   string
+	Include     string
+}
+type getter struct {
+	Query query
+	ID    int
+	Index int
+	Type  string
+}
+
+func main() {
+	fmt.Println("Generating code")
+
+	maxIndex := len(typeLetters)
+
+	text := bytes.Buffer{}
+
+	header, err := template.ParseFiles("./generate/header.gotxt")
+	if err != nil {
+		panic(err)
+	}
+	err = header.Execute(&text, struct{}{})
+	if err != nil {
+		panic(err)
+	}
+
+	filters, err := template.ParseFiles("./generate/query.gotxt")
+	if err != nil {
+		panic(err)
+	}
+	queryGetAll, err := template.ParseFiles("./generate/query_getall.gotxt")
+	if err != nil {
+		panic(err)
+	}
+	queryGet, err := template.ParseFiles("./generate/query_get.gotxt")
+	if err != nil {
+		panic(err)
+	}
+
+	for i := 0; i <= maxIndex; i++ {
+		types := ""
+		returnTypes := ""
+		fullTypes := ""
+		include := ""
+		returnAll := ""
+		if i > 0 {
+			types = "[" + strings.Join(typeLetters[:i], ", ") + "]"
+			returnTypes = "(*" + strings.Join(typeLetters[:i], ", *") + ")"
+			fullTypes = "[" + strings.Join(typeLetters[:i], " any, ") + " any]"
+			include = "include: []reflect.Type{typeOf[" + strings.Join(typeLetters[:i], "](), typeOf[") + "]()},"
+			for j := 0; j < i; j++ {
+				returnAll += fmt.Sprintf("(*%s)(q.Query.Get(q.ids[%d]))", typeLetters[j], j)
+				if j < i-1 {
+					returnAll += ", "
+				}
+			}
+		}
+		data := query{
+			Index:       i,
+			Types:       types,
+			TypesReturn: returnTypes,
+			TypesFull:   fullTypes,
+			ReturnAll:   returnAll,
+			Include:     include,
+		}
+		err = filters.Execute(&text, data)
+		if err != nil {
+			panic(err)
+		}
+		if i == 0 {
+			continue
+		}
+
+		err = queryGetAll.Execute(&text, data)
+		if err != nil {
+			panic(err)
+		}
+
+		for j := 0; j < i; j++ {
+			getterData := getter{
+				Query: data,
+				ID:    j + 1,
+				Index: j,
+				Type:  typeLetters[j],
+			}
+			err = queryGet.Execute(&text, getterData)
+			if err != nil {
+				panic(err)
+			}
+		}
+	}
+	if err := os.WriteFile("query_generated.go", text.Bytes(), 0666); err != nil {
+		panic(err)
+	}
+}
diff --git a/generic/generate/query.gotxt b/generic/generate/query.gotxt
new file mode 100644
index 0000000..c71c482
--- /dev/null
+++ b/generic/generate/query.gotxt
@@ -0,0 +1,74 @@
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter{{ .Index }} builds a [Query{{ .Index }}] query
+type Filter{{ .Index }}{{ .TypesFull }} struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery{{ .Index }} creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter{{ .Index }}{{ .TypesFull }}() *Filter{{ .Index }}{{ .Types }} {
+	return &Filter{{ .Index }}{{ .Types }}{
+		{{ .Include }}
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter{{ .Index }}{{ .Types }}) Optional(mask []reflect.Type) *Filter{{ .Index }}{{ .Types }} {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter{{ .Index }}{{ .Types }}) With(mask []reflect.Type) *Filter{{ .Index }}{{ .Types }} {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter{{ .Index }}{{ .Types }}) Without(mask []reflect.Type) *Filter{{ .Index }}{{ .Types }} {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query{{ .Index }}] query for iteration.
+func (q *Filter{{ .Index }}{{ .Types }}) Query(w *ecs.World) Query{{ .Index }}{{ .Types }} {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query{{ .Index }}{{ .Types }}{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter{{ .Index }}.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter{{ .Index }}.Query].
+func (q *Filter{{ .Index }}{{ .Types }}) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query{{ .Index }} is a generic query iterator for two components.
+//
+// Create one with [NewFilter{{ .Index }}] and [Filter{{ .Index }}.Query]
+type Query{{ .Index }}{{ .TypesFull }} struct {
+	ecs.Query
+	ids []ecs.ID
+}
diff --git a/generic/generate/query_get.gotxt b/generic/generate/query_get.gotxt
new file mode 100644
index 0000000..7011298
--- /dev/null
+++ b/generic/generate/query_get.gotxt
@@ -0,0 +1,5 @@
+
+// Get{{ .ID }} returns the first queried component for the current query position
+func (q *Query{{ .Query.Index }}{{ .Query.Types }}) Get{{ .ID }}() *{{ .Type }} {
+	return (*{{ .Type }})(q.Query.Get(q.ids[{{ .Index }}]))
+}
diff --git a/generic/generate/query_getall.gotxt b/generic/generate/query_getall.gotxt
new file mode 100644
index 0000000..792b729
--- /dev/null
+++ b/generic/generate/query_getall.gotxt
@@ -0,0 +1,5 @@
+
+// GetAll returns all queried components for the current query position
+func (q *Query{{ .Index }}{{ .Types }}) GetAll() {{ .TypesReturn }} {
+	return {{ .ReturnAll }}
+}
diff --git a/generic/mask.go b/generic/mask.go
new file mode 100644
index 0000000..90f8d4e
--- /dev/null
+++ b/generic/mask.go
@@ -0,0 +1,59 @@
+package generic
+
+import "reflect"
+
+// Mask1 creates a component type list for one component type.
+func Mask1[A any]() []reflect.Type {
+	return []reflect.Type{typeOf[A]()}
+}
+
+// Mask2 creates a component type list for two component types.
+func Mask2[A any, B any]() []reflect.Type {
+	return []reflect.Type{typeOf[A](), typeOf[B]()}
+}
+
+// Mask3 creates a component type list for three component types.
+func Mask3[A any, B any, C any]() []reflect.Type {
+	return []reflect.Type{
+		typeOf[A](), typeOf[B](), typeOf[C](),
+	}
+}
+
+// Mask4 creates a component type list for four component types.
+func Mask4[A any, B any, C any, D any]() []reflect.Type {
+	return []reflect.Type{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+	}
+}
+
+// Mask5 creates a component type list for five component types.
+func Mask5[A any, B any, C any, D any, E any]() []reflect.Type {
+	return []reflect.Type{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+		typeOf[E](),
+	}
+}
+
+// Mask6 creates a component type list for six component types.
+func Mask6[A any, B any, C any, D any, E any, F any]() []reflect.Type {
+	return []reflect.Type{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+		typeOf[E](), typeOf[F](),
+	}
+}
+
+// Mask7 creates a component type list for seven component types.
+func Mask7[A any, B any, C any, D any, E any, F any, G any]() []reflect.Type {
+	return []reflect.Type{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+		typeOf[E](), typeOf[F](), typeOf[G](),
+	}
+}
+
+// Mask8 creates a component type list for eight component types.
+func Mask8[A any, B any, C any, D any, E any, F any, G any, H any]() []reflect.Type {
+	return []reflect.Type{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+		typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H](),
+	}
+}
diff --git a/generic/query_generated.go b/generic/query_generated.go
new file mode 100644
index 0000000..2a6f367
--- /dev/null
+++ b/generic/query_generated.go
@@ -0,0 +1,893 @@
+package generic
+
+// Code generated by go generate; DO NOT EDIT.
+
+import (
+	"reflect"
+
+	"github.com/mlange-42/arche/ecs"
+)
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter0 builds a [Query0] query
+type Filter0 struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery0 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter0() *Filter0 {
+	return &Filter0{}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter0) Optional(mask []reflect.Type) *Filter0 {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter0) With(mask []reflect.Type) *Filter0 {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter0) Without(mask []reflect.Type) *Filter0 {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query0] query for iteration.
+func (q *Filter0) Query(w *ecs.World) Query0 {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query0{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter0.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter0.Query].
+func (q *Filter0) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query0 is a generic query iterator for two components.
+//
+// Create one with [NewFilter0] and [Filter0.Query]
+type Query0 struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter1 builds a [Query1] query
+type Filter1[A any] struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery1 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter1[A any]() *Filter1[A] {
+	return &Filter1[A]{
+		include: []reflect.Type{typeOf[A]()},
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter1[A]) Optional(mask []reflect.Type) *Filter1[A] {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter1[A]) With(mask []reflect.Type) *Filter1[A] {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter1[A]) Without(mask []reflect.Type) *Filter1[A] {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query1] query for iteration.
+func (q *Filter1[A]) Query(w *ecs.World) Query1[A] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query1[A]{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter1.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter1.Query].
+func (q *Filter1[A]) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query1 is a generic query iterator for two components.
+//
+// Create one with [NewFilter1] and [Filter1.Query]
+type Query1[A any] struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+// GetAll returns all queried components for the current query position
+func (q *Query1[A]) GetAll() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+// Get1 returns the first queried component for the current query position
+func (q *Query1[A]) Get1() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter2 builds a [Query2] query
+type Filter2[A any, B any] struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery2 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter2[A any, B any]() *Filter2[A, B] {
+	return &Filter2[A, B]{
+		include: []reflect.Type{typeOf[A](), typeOf[B]()},
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter2[A, B]) Optional(mask []reflect.Type) *Filter2[A, B] {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter2[A, B]) With(mask []reflect.Type) *Filter2[A, B] {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter2[A, B]) Without(mask []reflect.Type) *Filter2[A, B] {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query2] query for iteration.
+func (q *Filter2[A, B]) Query(w *ecs.World) Query2[A, B] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query2[A, B]{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter2.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter2.Query].
+func (q *Filter2[A, B]) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query2 is a generic query iterator for two components.
+//
+// Create one with [NewFilter2] and [Filter2.Query]
+type Query2[A any, B any] struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+// GetAll returns all queried components for the current query position
+func (q *Query2[A, B]) GetAll() (*A, *B) {
+	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1]))
+}
+
+// Get1 returns the first queried component for the current query position
+func (q *Query2[A, B]) Get1() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+// Get2 returns the first queried component for the current query position
+func (q *Query2[A, B]) Get2() *B {
+	return (*B)(q.Query.Get(q.ids[1]))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter3 builds a [Query3] query
+type Filter3[A any, B any, C any] struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery3 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter3[A any, B any, C any]() *Filter3[A, B, C] {
+	return &Filter3[A, B, C]{
+		include: []reflect.Type{typeOf[A](), typeOf[B](), typeOf[C]()},
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter3[A, B, C]) Optional(mask []reflect.Type) *Filter3[A, B, C] {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter3[A, B, C]) With(mask []reflect.Type) *Filter3[A, B, C] {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter3[A, B, C]) Without(mask []reflect.Type) *Filter3[A, B, C] {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query3] query for iteration.
+func (q *Filter3[A, B, C]) Query(w *ecs.World) Query3[A, B, C] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query3[A, B, C]{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter3.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter3.Query].
+func (q *Filter3[A, B, C]) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query3 is a generic query iterator for two components.
+//
+// Create one with [NewFilter3] and [Filter3.Query]
+type Query3[A any, B any, C any] struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+// GetAll returns all queried components for the current query position
+func (q *Query3[A, B, C]) GetAll() (*A, *B, *C) {
+	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1])), (*C)(q.Query.Get(q.ids[2]))
+}
+
+// Get1 returns the first queried component for the current query position
+func (q *Query3[A, B, C]) Get1() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+// Get2 returns the first queried component for the current query position
+func (q *Query3[A, B, C]) Get2() *B {
+	return (*B)(q.Query.Get(q.ids[1]))
+}
+
+// Get3 returns the first queried component for the current query position
+func (q *Query3[A, B, C]) Get3() *C {
+	return (*C)(q.Query.Get(q.ids[2]))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter4 builds a [Query4] query
+type Filter4[A any, B any, C any, D any] struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery4 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter4[A any, B any, C any, D any]() *Filter4[A, B, C, D] {
+	return &Filter4[A, B, C, D]{
+		include: []reflect.Type{typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D]()},
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter4[A, B, C, D]) Optional(mask []reflect.Type) *Filter4[A, B, C, D] {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter4[A, B, C, D]) With(mask []reflect.Type) *Filter4[A, B, C, D] {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter4[A, B, C, D]) Without(mask []reflect.Type) *Filter4[A, B, C, D] {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query4] query for iteration.
+func (q *Filter4[A, B, C, D]) Query(w *ecs.World) Query4[A, B, C, D] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query4[A, B, C, D]{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter4.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter4.Query].
+func (q *Filter4[A, B, C, D]) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query4 is a generic query iterator for two components.
+//
+// Create one with [NewFilter4] and [Filter4.Query]
+type Query4[A any, B any, C any, D any] struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+// GetAll returns all queried components for the current query position
+func (q *Query4[A, B, C, D]) GetAll() (*A, *B, *C, *D) {
+	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1])), (*C)(q.Query.Get(q.ids[2])), (*D)(q.Query.Get(q.ids[3]))
+}
+
+// Get1 returns the first queried component for the current query position
+func (q *Query4[A, B, C, D]) Get1() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+// Get2 returns the first queried component for the current query position
+func (q *Query4[A, B, C, D]) Get2() *B {
+	return (*B)(q.Query.Get(q.ids[1]))
+}
+
+// Get3 returns the first queried component for the current query position
+func (q *Query4[A, B, C, D]) Get3() *C {
+	return (*C)(q.Query.Get(q.ids[2]))
+}
+
+// Get4 returns the first queried component for the current query position
+func (q *Query4[A, B, C, D]) Get4() *D {
+	return (*D)(q.Query.Get(q.ids[3]))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter5 builds a [Query5] query
+type Filter5[A any, B any, C any, D any, E any] struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery5 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter5[A any, B any, C any, D any, E any]() *Filter5[A, B, C, D, E] {
+	return &Filter5[A, B, C, D, E]{
+		include: []reflect.Type{typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](), typeOf[E]()},
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter5[A, B, C, D, E]) Optional(mask []reflect.Type) *Filter5[A, B, C, D, E] {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter5[A, B, C, D, E]) With(mask []reflect.Type) *Filter5[A, B, C, D, E] {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter5[A, B, C, D, E]) Without(mask []reflect.Type) *Filter5[A, B, C, D, E] {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query5] query for iteration.
+func (q *Filter5[A, B, C, D, E]) Query(w *ecs.World) Query5[A, B, C, D, E] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query5[A, B, C, D, E]{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter5.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter5.Query].
+func (q *Filter5[A, B, C, D, E]) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query5 is a generic query iterator for two components.
+//
+// Create one with [NewFilter5] and [Filter5.Query]
+type Query5[A any, B any, C any, D any, E any] struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+// GetAll returns all queried components for the current query position
+func (q *Query5[A, B, C, D, E]) GetAll() (*A, *B, *C, *D, *E) {
+	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1])), (*C)(q.Query.Get(q.ids[2])), (*D)(q.Query.Get(q.ids[3])), (*E)(q.Query.Get(q.ids[4]))
+}
+
+// Get1 returns the first queried component for the current query position
+func (q *Query5[A, B, C, D, E]) Get1() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+// Get2 returns the first queried component for the current query position
+func (q *Query5[A, B, C, D, E]) Get2() *B {
+	return (*B)(q.Query.Get(q.ids[1]))
+}
+
+// Get3 returns the first queried component for the current query position
+func (q *Query5[A, B, C, D, E]) Get3() *C {
+	return (*C)(q.Query.Get(q.ids[2]))
+}
+
+// Get4 returns the first queried component for the current query position
+func (q *Query5[A, B, C, D, E]) Get4() *D {
+	return (*D)(q.Query.Get(q.ids[3]))
+}
+
+// Get5 returns the first queried component for the current query position
+func (q *Query5[A, B, C, D, E]) Get5() *E {
+	return (*E)(q.Query.Get(q.ids[4]))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter6 builds a [Query6] query
+type Filter6[A any, B any, C any, D any, E any, F any] struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery6 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter6[A any, B any, C any, D any, E any, F any]() *Filter6[A, B, C, D, E, F] {
+	return &Filter6[A, B, C, D, E, F]{
+		include: []reflect.Type{typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](), typeOf[E](), typeOf[F]()},
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter6[A, B, C, D, E, F]) Optional(mask []reflect.Type) *Filter6[A, B, C, D, E, F] {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter6[A, B, C, D, E, F]) With(mask []reflect.Type) *Filter6[A, B, C, D, E, F] {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter6[A, B, C, D, E, F]) Without(mask []reflect.Type) *Filter6[A, B, C, D, E, F] {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query6] query for iteration.
+func (q *Filter6[A, B, C, D, E, F]) Query(w *ecs.World) Query6[A, B, C, D, E, F] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query6[A, B, C, D, E, F]{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter6.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter6.Query].
+func (q *Filter6[A, B, C, D, E, F]) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query6 is a generic query iterator for two components.
+//
+// Create one with [NewFilter6] and [Filter6.Query]
+type Query6[A any, B any, C any, D any, E any, F any] struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+// GetAll returns all queried components for the current query position
+func (q *Query6[A, B, C, D, E, F]) GetAll() (*A, *B, *C, *D, *E, *F) {
+	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1])), (*C)(q.Query.Get(q.ids[2])), (*D)(q.Query.Get(q.ids[3])), (*E)(q.Query.Get(q.ids[4])), (*F)(q.Query.Get(q.ids[5]))
+}
+
+// Get1 returns the first queried component for the current query position
+func (q *Query6[A, B, C, D, E, F]) Get1() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+// Get2 returns the first queried component for the current query position
+func (q *Query6[A, B, C, D, E, F]) Get2() *B {
+	return (*B)(q.Query.Get(q.ids[1]))
+}
+
+// Get3 returns the first queried component for the current query position
+func (q *Query6[A, B, C, D, E, F]) Get3() *C {
+	return (*C)(q.Query.Get(q.ids[2]))
+}
+
+// Get4 returns the first queried component for the current query position
+func (q *Query6[A, B, C, D, E, F]) Get4() *D {
+	return (*D)(q.Query.Get(q.ids[3]))
+}
+
+// Get5 returns the first queried component for the current query position
+func (q *Query6[A, B, C, D, E, F]) Get5() *E {
+	return (*E)(q.Query.Get(q.ids[4]))
+}
+
+// Get6 returns the first queried component for the current query position
+func (q *Query6[A, B, C, D, E, F]) Get6() *F {
+	return (*F)(q.Query.Get(q.ids[5]))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter7 builds a [Query7] query
+type Filter7[A any, B any, C any, D any, E any, F any, G any] struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery7 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter7[A any, B any, C any, D any, E any, F any, G any]() *Filter7[A, B, C, D, E, F, G] {
+	return &Filter7[A, B, C, D, E, F, G]{
+		include: []reflect.Type{typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](), typeOf[E](), typeOf[F](), typeOf[G]()},
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter7[A, B, C, D, E, F, G]) Optional(mask []reflect.Type) *Filter7[A, B, C, D, E, F, G] {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter7[A, B, C, D, E, F, G]) With(mask []reflect.Type) *Filter7[A, B, C, D, E, F, G] {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter7[A, B, C, D, E, F, G]) Without(mask []reflect.Type) *Filter7[A, B, C, D, E, F, G] {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query7] query for iteration.
+func (q *Filter7[A, B, C, D, E, F, G]) Query(w *ecs.World) Query7[A, B, C, D, E, F, G] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query7[A, B, C, D, E, F, G]{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter7.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter7.Query].
+func (q *Filter7[A, B, C, D, E, F, G]) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query7 is a generic query iterator for two components.
+//
+// Create one with [NewFilter7] and [Filter7.Query]
+type Query7[A any, B any, C any, D any, E any, F any, G any] struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+// GetAll returns all queried components for the current query position
+func (q *Query7[A, B, C, D, E, F, G]) GetAll() (*A, *B, *C, *D, *E, *F, *G) {
+	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1])), (*C)(q.Query.Get(q.ids[2])), (*D)(q.Query.Get(q.ids[3])), (*E)(q.Query.Get(q.ids[4])), (*F)(q.Query.Get(q.ids[5])), (*G)(q.Query.Get(q.ids[6]))
+}
+
+// Get1 returns the first queried component for the current query position
+func (q *Query7[A, B, C, D, E, F, G]) Get1() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+// Get2 returns the first queried component for the current query position
+func (q *Query7[A, B, C, D, E, F, G]) Get2() *B {
+	return (*B)(q.Query.Get(q.ids[1]))
+}
+
+// Get3 returns the first queried component for the current query position
+func (q *Query7[A, B, C, D, E, F, G]) Get3() *C {
+	return (*C)(q.Query.Get(q.ids[2]))
+}
+
+// Get4 returns the first queried component for the current query position
+func (q *Query7[A, B, C, D, E, F, G]) Get4() *D {
+	return (*D)(q.Query.Get(q.ids[3]))
+}
+
+// Get5 returns the first queried component for the current query position
+func (q *Query7[A, B, C, D, E, F, G]) Get5() *E {
+	return (*E)(q.Query.Get(q.ids[4]))
+}
+
+// Get6 returns the first queried component for the current query position
+func (q *Query7[A, B, C, D, E, F, G]) Get6() *F {
+	return (*F)(q.Query.Get(q.ids[5]))
+}
+
+// Get7 returns the first queried component for the current query position
+func (q *Query7[A, B, C, D, E, F, G]) Get7() *G {
+	return (*G)(q.Query.Get(q.ids[6]))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter8 builds a [Query8] query
+type Filter8[A any, B any, C any, D any, E any, F any, G any, H any] struct {
+	include  []reflect.Type
+	optional []reflect.Type
+	exclude  []reflect.Type
+	compiled compiledQuery
+}
+
+// NewQuery8 creates a generic filter for two components.
+//
+// See also [ecs.World.Query].
+func NewFilter8[A any, B any, C any, D any, E any, F any, G any, H any]() *Filter8[A, B, C, D, E, F, G, H] {
+	return &Filter8[A, B, C, D, E, F, G, H]{
+		include: []reflect.Type{typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](), typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H]()},
+	}
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+//
+// Only affects component types that were specified in the query.
+func (q *Filter8[A, B, C, D, E, F, G, H]) Optional(mask []reflect.Type) *Filter8[A, B, C, D, E, F, G, H] {
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds more required components that are not accessible using Get... methods.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter8[A, B, C, D, E, F, G, H]) With(mask []reflect.Type) *Filter8[A, B, C, D, E, F, G, H] {
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q *Filter8[A, B, C, D, E, F, G, H]) Without(mask []reflect.Type) *Filter8[A, B, C, D, E, F, G, H] {
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query8] query for iteration.
+func (q *Filter8[A, B, C, D, E, F, G, H]) Query(w *ecs.World) Query8[A, B, C, D, E, F, G, H] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return Query8[A, B, C, D, E, F, G, H]{
+		w.Query(q.compiled.Filter()),
+		q.compiled.Ids,
+	}
+}
+
+// Filter generates and return the [ecs.Filter] used after [Filter8.Query].
+//
+// Can be passed to [ecs.World.Query].
+// For the intended generic use, however, generate a generic query with [Filter8.Query].
+func (q *Filter8[A, B, C, D, E, F, G, H]) Filter(w *ecs.World) ecs.MaskPair {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude)
+	return q.compiled.Filter()
+}
+
+// Query8 is a generic query iterator for two components.
+//
+// Create one with [NewFilter8] and [Filter8.Query]
+type Query8[A any, B any, C any, D any, E any, F any, G any, H any] struct {
+	ecs.Query
+	ids []ecs.ID
+}
+
+// GetAll returns all queried components for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) GetAll() (*A, *B, *C, *D, *E, *F, *G, *H) {
+	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1])), (*C)(q.Query.Get(q.ids[2])), (*D)(q.Query.Get(q.ids[3])), (*E)(q.Query.Get(q.ids[4])), (*F)(q.Query.Get(q.ids[5])), (*G)(q.Query.Get(q.ids[6])), (*H)(q.Query.Get(q.ids[7]))
+}
+
+// Get1 returns the first queried component for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) Get1() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
+}
+
+// Get2 returns the first queried component for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) Get2() *B {
+	return (*B)(q.Query.Get(q.ids[1]))
+}
+
+// Get3 returns the first queried component for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) Get3() *C {
+	return (*C)(q.Query.Get(q.ids[2]))
+}
+
+// Get4 returns the first queried component for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) Get4() *D {
+	return (*D)(q.Query.Get(q.ids[3]))
+}
+
+// Get5 returns the first queried component for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) Get5() *E {
+	return (*E)(q.Query.Get(q.ids[4]))
+}
+
+// Get6 returns the first queried component for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) Get6() *F {
+	return (*F)(q.Query.Get(q.ids[5]))
+}
+
+// Get7 returns the first queried component for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) Get7() *G {
+	return (*G)(q.Query.Get(q.ids[6]))
+}
+
+// Get8 returns the first queried component for the current query position
+func (q *Query8[A, B, C, D, E, F, G, H]) Get8() *H {
+	return (*H)(q.Query.Get(q.ids[7]))
+}
diff --git a/generic/query_generic.go b/generic/query_generic.go
deleted file mode 100644
index a6fa123..0000000
--- a/generic/query_generic.go
+++ /dev/null
@@ -1,878 +0,0 @@
-package generic
-
-import (
-	"reflect"
-
-	"github.com/mlange-42/arche/ecs"
-)
-
-type compiledQuery struct {
-	mask     ecs.Mask
-	exclude  ecs.Mask
-	Ids      []ecs.ID
-	compiled bool
-}
-
-func (q *compiledQuery) Compile(w *ecs.World, include, optional, exclude []reflect.Type) {
-	if q.compiled {
-		return
-	}
-	q.Ids = toIds(w, include)
-	q.mask = toMaskOptional(w, q.Ids, optional)
-	q.exclude = toMask(w, exclude)
-	q.compiled = true
-}
-
-func (q *compiledQuery) MaskPair() ecs.MaskPair {
-	return ecs.MaskPair{
-		Mask:    q.mask,
-		Exclude: q.exclude,
-	}
-}
-
-// Q0Builder builds a [Q0] query
-type Q0Builder struct {
-	include  []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query0 creates a generic query for no components.
-//
-// See also [ecs.World.Query].
-func Query0() *Q0Builder {
-	return &Q0Builder{}
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q0Builder) With(mask []reflect.Type) *Q0Builder {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q0Builder) Without(mask []reflect.Type) *Q0Builder {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q0 query for iteration.
-func (q *Q0Builder) Build(w *ecs.World) Q0 {
-	q.compiled.Compile(w, q.include, []reflect.Type{}, q.exclude)
-	return Q0{
-		w.Query(q.compiled.MaskPair()),
-	}
-}
-
-// Q0 is a generic query for no components.
-//
-// Create one with [Query0]
-type Q0 struct {
-	ecs.Query
-}
-
-// Q1Builder builds a [Q1] query
-type Q1Builder[A any] struct {
-	include  []reflect.Type
-	optional []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query1 creates a generic query for one component.
-//
-// See also [ecs.World.Query].
-func Query1[A any]() *Q1Builder[A] {
-	return &Q1Builder[A]{
-		include: []reflect.Type{typeOf[A]()},
-	}
-}
-
-// Optional makes some of the query's components optional.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-//
-// Only affects component types that were specified in the query.
-func (q *Q1Builder[A]) Optional(mask []reflect.Type) *Q1Builder[A] {
-	q.optional = append(q.optional, mask...)
-	return q
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q1Builder[A]) With(mask []reflect.Type) *Q1Builder[A] {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q1Builder[A]) Without(mask []reflect.Type) *Q1Builder[A] {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q1 query for iteration.
-func (q *Q1Builder[A]) Build(w *ecs.World) Q1[A] {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude)
-	return Q1[A]{
-		w.Query(q.compiled.MaskPair()),
-		q.compiled.Ids[0],
-	}
-}
-
-// Q1 is a generic query for one component.
-//
-// Create one with [Query1]
-type Q1[A any] struct {
-	ecs.Query
-	id ecs.ID
-}
-
-// Get1 returns the first queried component for the current query position
-func (q *Q1[A]) Get1() *A {
-	return (*A)(q.Query.Get(q.id))
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-// Q2Builder builds a [Q2] query
-type Q2Builder[A any, B any] struct {
-	include  []reflect.Type
-	optional []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query2 creates a generic query for two components.
-//
-// See also [ecs.World.Query].
-func Query2[A any, B any]() *Q2Builder[A, B] {
-	return &Q2Builder[A, B]{
-		include: []reflect.Type{typeOf[A](), typeOf[B]()},
-	}
-}
-
-// Optional makes some of the query's components optional.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-//
-// Only affects component types that were specified in the query.
-func (q *Q2Builder[A, B]) Optional(mask []reflect.Type) *Q2Builder[A, B] {
-	q.optional = append(q.optional, mask...)
-	return q
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q2Builder[A, B]) With(mask []reflect.Type) *Q2Builder[A, B] {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q2Builder[A, B]) Without(mask []reflect.Type) *Q2Builder[A, B] {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q2 query for iteration.
-func (q *Q2Builder[A, B]) Build(w *ecs.World) Q2[A, B] {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude)
-	return Q2[A, B]{
-		w.Query(q.compiled.MaskPair()),
-		q.compiled.Ids,
-	}
-}
-
-// Q2 is a generic query for two components.
-//
-// Create one with [Query2]
-type Q2[A any, B any] struct {
-	ecs.Query
-	ids []ecs.ID
-}
-
-// GetAll returns all queried components for the current query position
-func (q *Q2[A, B]) GetAll() (*A, *B) {
-	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1]))
-}
-
-// Get1 returns the first queried component for the current query position
-func (q *Q2[A, B]) Get1() *A {
-	return (*A)(q.Query.Get(q.ids[0]))
-}
-
-// Get2 returns the second queried component for the current query position
-func (q *Q2[A, B]) Get2() *B {
-	return (*B)(q.Query.Get(q.ids[1]))
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-// Q3Builder builds a [Q3] query
-type Q3Builder[A any, B any, C any] struct {
-	include  []reflect.Type
-	optional []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query3 creates a generic query for three components.
-//
-// See also [ecs.World.Query].
-func Query3[A any, B any, C any]() *Q3Builder[A, B, C] {
-	return &Q3Builder[A, B, C]{
-		include: []reflect.Type{typeOf[A](), typeOf[B](), typeOf[C]()},
-	}
-}
-
-// Optional makes some of the query's components optional.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-//
-// Only affects component types that were specified in the query.
-func (q *Q3Builder[A, B, C]) Optional(mask []reflect.Type) *Q3Builder[A, B, C] {
-	q.optional = append(q.optional, mask...)
-	return q
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q3Builder[A, B, C]) With(mask []reflect.Type) *Q3Builder[A, B, C] {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q3Builder[A, B, C]) Without(mask []reflect.Type) *Q3Builder[A, B, C] {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q3 query for iteration.
-func (q *Q3Builder[A, B, C]) Build(w *ecs.World) Q3[A, B, C] {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude)
-	return Q3[A, B, C]{
-		w.Query(q.compiled.MaskPair()),
-		q.compiled.Ids,
-	}
-}
-
-// Q3 is a generic query for three components.
-//
-// Create one with [Query3]
-type Q3[A any, B any, C any] struct {
-	ecs.Query
-	ids []ecs.ID
-}
-
-// GetAll returns all queried components for the current query position
-func (q *Q3[A, B, C]) GetAll() (*A, *B, *C) {
-	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1])), (*C)(q.Query.Get(q.ids[2]))
-}
-
-// Get1 returns the first queried component for the current query position
-func (q *Q3[A, B, C]) Get1() *A {
-	return (*A)(q.Query.Get(q.ids[0]))
-}
-
-// Get2 returns the second queried component for the current query position
-func (q *Q3[A, B, C]) Get2() *B {
-	return (*B)(q.Query.Get(q.ids[1]))
-}
-
-// Get3 returns the third queried component for the current query position
-func (q *Q3[A, B, C]) Get3() *C {
-	return (*C)(q.Query.Get(q.ids[2]))
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-// Q4Builder builds a [Q4] query
-type Q4Builder[A any, B any, C any, D any] struct {
-	include  []reflect.Type
-	optional []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query4 creates a generic query for four components.
-//
-// See also [ecs.World.Query].
-func Query4[A any, B any, C any, D any]() *Q4Builder[A, B, C, D] {
-	return &Q4Builder[A, B, C, D]{
-		include: []reflect.Type{typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D]()},
-	}
-}
-
-// Optional makes some of the query's components optional.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-//
-// Only affects component types that were specified in the query.
-func (q *Q4Builder[A, B, C, D]) Optional(mask []reflect.Type) *Q4Builder[A, B, C, D] {
-	q.optional = append(q.optional, mask...)
-	return q
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q4Builder[A, B, C, D]) With(mask []reflect.Type) *Q4Builder[A, B, C, D] {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q4Builder[A, B, C, D]) Without(mask []reflect.Type) *Q4Builder[A, B, C, D] {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q4 query for iteration.
-func (q *Q4Builder[A, B, C, D]) Build(w *ecs.World) Q4[A, B, C, D] {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude)
-	return Q4[A, B, C, D]{
-		w.Query(q.compiled.MaskPair()),
-		q.compiled.Ids,
-	}
-}
-
-// Q4 is a generic query for four components.
-//
-// Create one with [Query4]
-type Q4[A any, B any, C any, D any] struct {
-	ecs.Query
-	ids []ecs.ID
-}
-
-// GetAll returns all queried components for the current query position
-func (q *Q4[A, B, C, D]) GetAll() (*A, *B, *C, *D) {
-	return (*A)(q.Query.Get(q.ids[0])),
-		(*B)(q.Query.Get(q.ids[1])),
-		(*C)(q.Query.Get(q.ids[2])),
-		(*D)(q.Query.Get(q.ids[3]))
-}
-
-// Get1 returns the first queried component for the current query position
-func (q *Q4[A, B, C, D]) Get1() *A {
-	return (*A)(q.Query.Get(q.ids[0]))
-}
-
-// Get2 returns the second queried component for the current query position
-func (q *Q4[A, B, C, D]) Get2() *B {
-	return (*B)(q.Query.Get(q.ids[1]))
-}
-
-// Get3 returns the third queried component for the current query position
-func (q *Q4[A, B, C, D]) Get3() *C {
-	return (*C)(q.Query.Get(q.ids[2]))
-}
-
-// Get4 returns the fourth queried component for the current query position
-func (q *Q4[A, B, C, D]) Get4() *D {
-	return (*D)(q.Query.Get(q.ids[3]))
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-// Q5Builder builds a [Q5] query
-type Q5Builder[A any, B any, C any, D any, E any] struct {
-	include  []reflect.Type
-	optional []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query5 creates a generic query for five components.
-//
-// See also [ecs.World.Query].
-func Query5[A any, B any, C any, D any, E any]() *Q5Builder[A, B, C, D, E] {
-	return &Q5Builder[A, B, C, D, E]{
-		include: []reflect.Type{
-			typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-			typeOf[E](),
-		},
-	}
-}
-
-// Optional makes some of the query's components optional.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-//
-// Only affects component types that were specified in the query.
-func (q *Q5Builder[A, B, C, D, E]) Optional(mask []reflect.Type) *Q5Builder[A, B, C, D, E] {
-	q.optional = append(q.optional, mask...)
-	return q
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q5Builder[A, B, C, D, E]) With(mask []reflect.Type) *Q5Builder[A, B, C, D, E] {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q5Builder[A, B, C, D, E]) Without(mask []reflect.Type) *Q5Builder[A, B, C, D, E] {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q5 query for iteration.
-func (q *Q5Builder[A, B, C, D, E]) Build(w *ecs.World) Q5[A, B, C, D, E] {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude)
-	return Q5[A, B, C, D, E]{
-		w.Query(q.compiled.MaskPair()),
-		q.compiled.Ids,
-	}
-}
-
-// Q5 is a generic query for five components.
-//
-// Create one with [Query5]
-type Q5[A any, B any, C any, D any, E any] struct {
-	ecs.Query
-	ids []ecs.ID
-}
-
-// GetAll returns all queried components for the current query position
-func (q *Q5[A, B, C, D, E]) GetAll() (*A, *B, *C, *D, *E) {
-	return (*A)(q.Query.Get(q.ids[0])),
-		(*B)(q.Query.Get(q.ids[1])),
-		(*C)(q.Query.Get(q.ids[2])),
-		(*D)(q.Query.Get(q.ids[3])),
-		(*E)(q.Query.Get(q.ids[4]))
-}
-
-// Get1 returns the first queried component for the current query position
-func (q *Q5[A, B, C, D, E]) Get1() *A {
-	return (*A)(q.Query.Get(q.ids[0]))
-}
-
-// Get2 returns the second queried component for the current query position
-func (q *Q5[A, B, C, D, E]) Get2() *B {
-	return (*B)(q.Query.Get(q.ids[1]))
-}
-
-// Get3 returns the third queried component for the current query position
-func (q *Q5[A, B, C, D, E]) Get3() *C {
-	return (*C)(q.Query.Get(q.ids[2]))
-}
-
-// Get4 returns the fourth queried component for the current query position
-func (q *Q5[A, B, C, D, E]) Get4() *D {
-	return (*D)(q.Query.Get(q.ids[3]))
-}
-
-// Get5 returns the fifth queried component for the current query position
-func (q *Q5[A, B, C, D, E]) Get5() *E {
-	return (*E)(q.Query.Get(q.ids[4]))
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-// Q6Builder builds a [Q6] query
-type Q6Builder[A any, B any, C any, D any, E any, F any] struct {
-	include  []reflect.Type
-	optional []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query6 creates a generic query for six components.
-//
-// See also [ecs.World.Query].
-func Query6[A any, B any, C any, D any, E any, F any]() *Q6Builder[A, B, C, D, E, F] {
-	return &Q6Builder[A, B, C, D, E, F]{
-		include: []reflect.Type{
-			typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-			typeOf[E](), typeOf[F](),
-		},
-	}
-}
-
-// Optional makes some of the query's components optional.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-//
-// Only affects component types that were specified in the query.
-func (q *Q6Builder[A, B, C, D, E, F]) Optional(mask []reflect.Type) *Q6Builder[A, B, C, D, E, F] {
-	q.optional = append(q.optional, mask...)
-	return q
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q6Builder[A, B, C, D, E, F]) With(mask []reflect.Type) *Q6Builder[A, B, C, D, E, F] {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q6Builder[A, B, C, D, E, F]) Without(mask []reflect.Type) *Q6Builder[A, B, C, D, E, F] {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q6 query for iteration.
-func (q *Q6Builder[A, B, C, D, E, F]) Build(w *ecs.World) Q6[A, B, C, D, E, F] {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude)
-	return Q6[A, B, C, D, E, F]{
-		w.Query(q.compiled.MaskPair()),
-		q.compiled.Ids,
-	}
-}
-
-// Q6 is a generic query for six components.
-//
-// Create one with [Query6]
-type Q6[A any, B any, C any, D any, E any, F any] struct {
-	ecs.Query
-	ids []ecs.ID
-}
-
-// GetAll returns all queried components for the current query position
-func (q *Q6[A, B, C, D, E, F]) GetAll() (*A, *B, *C, *D, *E, *F) {
-	return (*A)(q.Query.Get(q.ids[0])),
-		(*B)(q.Query.Get(q.ids[1])),
-		(*C)(q.Query.Get(q.ids[2])),
-		(*D)(q.Query.Get(q.ids[3])),
-		(*E)(q.Query.Get(q.ids[4])),
-		(*F)(q.Query.Get(q.ids[5]))
-}
-
-// Get1 returns the first queried component for the current query position
-func (q *Q6[A, B, C, D, E, F]) Get1() *A {
-	return (*A)(q.Query.Get(q.ids[0]))
-}
-
-// Get2 returns the second queried component for the current query position
-func (q *Q6[A, B, C, D, E, F]) Get2() *B {
-	return (*B)(q.Query.Get(q.ids[1]))
-}
-
-// Get3 returns the third queried component for the current query position
-func (q *Q6[A, B, C, D, E, F]) Get3() *C {
-	return (*C)(q.Query.Get(q.ids[2]))
-}
-
-// Get4 returns the fourth queried component for the current query position
-func (q *Q6[A, B, C, D, E, F]) Get4() *D {
-	return (*D)(q.Query.Get(q.ids[3]))
-}
-
-// Get5 returns the fifth queried component for the current query position
-func (q *Q6[A, B, C, D, E, F]) Get5() *E {
-	return (*E)(q.Query.Get(q.ids[4]))
-}
-
-// Get6 returns the sixth queried component for the current query position
-func (q *Q6[A, B, C, D, E, F]) Get6() *F {
-	return (*F)(q.Query.Get(q.ids[5]))
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-// Q7Builder builds a [Q7] query
-type Q7Builder[A any, B any, C any, D any, E any, F any, G any] struct {
-	include  []reflect.Type
-	optional []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query7 creates a generic query for seven components.
-//
-// See also [ecs.World.Query].
-func Query7[A any, B any, C any, D any, E any, F any, G any]() *Q7Builder[A, B, C, D, E, F, G] {
-	return &Q7Builder[A, B, C, D, E, F, G]{
-		include: []reflect.Type{
-			typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-			typeOf[E](), typeOf[F](), typeOf[G](),
-		},
-	}
-}
-
-// Optional makes some of the query's components optional.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-//
-// Only affects component types that were specified in the query.
-func (q *Q7Builder[A, B, C, D, E, F, G]) Optional(mask []reflect.Type) *Q7Builder[A, B, C, D, E, F, G] {
-	q.optional = append(q.optional, mask...)
-	return q
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q7Builder[A, B, C, D, E, F, G]) With(mask []reflect.Type) *Q7Builder[A, B, C, D, E, F, G] {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q7Builder[A, B, C, D, E, F, G]) Without(mask []reflect.Type) *Q7Builder[A, B, C, D, E, F, G] {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q7 query for iteration.
-func (q *Q7Builder[A, B, C, D, E, F, G]) Build(w *ecs.World) Q7[A, B, C, D, E, F, G] {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude)
-	return Q7[A, B, C, D, E, F, G]{
-		w.Query(q.compiled.MaskPair()),
-		q.compiled.Ids,
-	}
-}
-
-// Q7 is a generic query for seven components.
-//
-// Create one with [Query7]
-type Q7[A any, B any, C any, D any, E any, F any, G any] struct {
-	ecs.Query
-	ids []ecs.ID
-}
-
-// GetAll returns all queried components for the current query position
-func (q *Q7[A, B, C, D, E, F, G]) GetAll() (*A, *B, *C, *D, *E, *F, *G) {
-	return (*A)(q.Query.Get(q.ids[0])),
-		(*B)(q.Query.Get(q.ids[1])),
-		(*C)(q.Query.Get(q.ids[2])),
-		(*D)(q.Query.Get(q.ids[3])),
-		(*E)(q.Query.Get(q.ids[4])),
-		(*F)(q.Query.Get(q.ids[5])),
-		(*G)(q.Query.Get(q.ids[6]))
-}
-
-// Get1 returns the first queried component for the current query position
-func (q *Q7[A, B, C, D, E, F, G]) Get1() *A {
-	return (*A)(q.Query.Get(q.ids[0]))
-}
-
-// Get2 returns the second queried component for the current query position
-func (q *Q7[A, B, C, D, E, F, G]) Get2() *B {
-	return (*B)(q.Query.Get(q.ids[1]))
-}
-
-// Get3 returns the third queried component for the current query position
-func (q *Q7[A, B, C, D, E, F, G]) Get3() *C {
-	return (*C)(q.Query.Get(q.ids[2]))
-}
-
-// Get4 returns the fourth queried component for the current query position
-func (q *Q7[A, B, C, D, E, F, G]) Get4() *D {
-	return (*D)(q.Query.Get(q.ids[3]))
-}
-
-// Get5 returns the fifth queried component for the current query position
-func (q *Q7[A, B, C, D, E, F, G]) Get5() *E {
-	return (*E)(q.Query.Get(q.ids[4]))
-}
-
-// Get6 returns the sixth queried component for the current query position
-func (q *Q7[A, B, C, D, E, F, G]) Get6() *F {
-	return (*F)(q.Query.Get(q.ids[5]))
-}
-
-// Get7 returns the seventh queried component for the current query position
-func (q *Q7[A, B, C, D, E, F, G]) Get7() *G {
-	return (*G)(q.Query.Get(q.ids[6]))
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-// Q8Builder builds a [Q8] query
-type Q8Builder[A any, B any, C any, D any, E any, F any, G any, H any] struct {
-	include  []reflect.Type
-	optional []reflect.Type
-	exclude  []reflect.Type
-	compiled compiledQuery
-}
-
-// Query8 creates a generic query for eight components.
-//
-// See also [ecs.World.Query].
-func Query8[A any, B any, C any, D any, E any, F any, G any, H any]() *Q8Builder[A, B, C, D, E, F, G, H] {
-	return &Q8Builder[A, B, C, D, E, F, G, H]{
-		include: []reflect.Type{
-			typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-			typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H](),
-		},
-	}
-}
-
-// Optional makes some of the query's components optional.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-//
-// Only affects component types that were specified in the query.
-func (q *Q8Builder[A, B, C, D, E, F, G, H]) Optional(mask []reflect.Type) *Q8Builder[A, B, C, D, E, F, G, H] {
-	q.optional = append(q.optional, mask...)
-	return q
-}
-
-// With adds more required components that are not accessible using Get... methods.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q8Builder[A, B, C, D, E, F, G, H]) With(mask []reflect.Type) *Q8Builder[A, B, C, D, E, F, G, H] {
-	q.include = append(q.include, mask...)
-	return q
-}
-
-// Without excludes entities with any of the given components from the query.
-//
-// Create the required mask with [Mask1], [Mask2], etc.
-func (q *Q8Builder[A, B, C, D, E, F, G, H]) Without(mask []reflect.Type) *Q8Builder[A, B, C, D, E, F, G, H] {
-	q.exclude = append(q.exclude, mask...)
-	return q
-}
-
-// Build builds a Q8 query for iteration.
-func (q *Q8Builder[A, B, C, D, E, F, G, H]) Build(w *ecs.World) Q8[A, B, C, D, E, F, G, H] {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude)
-	return Q8[A, B, C, D, E, F, G, H]{
-		w.Query(q.compiled.MaskPair()),
-		q.compiled.Ids,
-	}
-}
-
-// Q8 is a generic query for seven components.
-//
-// Create one with [Query8]
-type Q8[A any, B any, C any, D any, E any, F any, G any, H any] struct {
-	ecs.Query
-	ids []ecs.ID
-}
-
-// GetAll returns all queried components for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) GetAll() (*A, *B, *C, *D, *E, *F, *G, *H) {
-	return (*A)(q.Query.Get(q.ids[0])),
-		(*B)(q.Query.Get(q.ids[1])),
-		(*C)(q.Query.Get(q.ids[2])),
-		(*D)(q.Query.Get(q.ids[3])),
-		(*E)(q.Query.Get(q.ids[4])),
-		(*F)(q.Query.Get(q.ids[5])),
-		(*G)(q.Query.Get(q.ids[6])),
-		(*H)(q.Query.Get(q.ids[7]))
-}
-
-// Get1 returns the first queried component for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) Get1() *A {
-	return (*A)(q.Query.Get(q.ids[0]))
-}
-
-// Get2 returns the second queried component for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) Get2() *B {
-	return (*B)(q.Query.Get(q.ids[1]))
-}
-
-// Get3 returns the third queried component for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) Get3() *C {
-	return (*C)(q.Query.Get(q.ids[2]))
-}
-
-// Get4 returns the fourth queried component for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) Get4() *D {
-	return (*D)(q.Query.Get(q.ids[3]))
-}
-
-// Get5 returns the fifth queried component for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) Get5() *E {
-	return (*E)(q.Query.Get(q.ids[4]))
-}
-
-// Get6 returns the sixth queried component for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) Get6() *F {
-	return (*F)(q.Query.Get(q.ids[5]))
-}
-
-// Get7 returns the seventh queried component for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) Get7() *G {
-	return (*G)(q.Query.Get(q.ids[6]))
-}
-
-// Get8 returns the eighth queried component for the current query position
-func (q *Q8[A, B, C, D, E, F, G, H]) Get8() *H {
-	return (*H)(q.Query.Get(q.ids[7]))
-}
-
-// Mask1 creates a component type list for one component type.
-func Mask1[A any]() []reflect.Type {
-	return []reflect.Type{typeOf[A]()}
-}
-
-// Mask2 creates a component type list for two component types.
-func Mask2[A any, B any]() []reflect.Type {
-	return []reflect.Type{typeOf[A](), typeOf[B]()}
-}
-
-// Mask3 creates a component type list for three component types.
-func Mask3[A any, B any, C any]() []reflect.Type {
-	return []reflect.Type{
-		typeOf[A](), typeOf[B](), typeOf[C](),
-	}
-}
-
-// Mask4 creates a component type list for four component types.
-func Mask4[A any, B any, C any, D any]() []reflect.Type {
-	return []reflect.Type{
-		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-	}
-}
-
-// Mask5 creates a component type list for five component types.
-func Mask5[A any, B any, C any, D any, E any]() []reflect.Type {
-	return []reflect.Type{
-		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-		typeOf[E](),
-	}
-}
-
-// Mask6 creates a component type list for six component types.
-func Mask6[A any, B any, C any, D any, E any, F any]() []reflect.Type {
-	return []reflect.Type{
-		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-		typeOf[E](), typeOf[F](),
-	}
-}
-
-// Mask7 creates a component type list for seven component types.
-func Mask7[A any, B any, C any, D any, E any, F any, G any]() []reflect.Type {
-	return []reflect.Type{
-		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-		typeOf[E](), typeOf[F](), typeOf[G](),
-	}
-}
-
-// Mask8 creates a component type list for eight component types.
-func Mask8[A any, B any, C any, D any, E any, F any, G any, H any]() []reflect.Type {
-	return []reflect.Type{
-		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
-		typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H](),
-	}
-}


Test patch:
diff --git a/benchmark/iterate/arche_test.go b/benchmark/iterate/arche_test.go
index 8341c2b..6c9d3a9 100644
--- a/benchmark/iterate/arche_test.go
+++ b/benchmark/iterate/arche_test.go
@@ -67,12 +67,12 @@ func runArcheQueryGeneric(b *testing.B, count int) {
 		entity := world.NewEntity()
 		world.Add(entity, posID, rotID)
 	}
-	query := generic.Query2[position, rotation]()
+	query := generic.NewFilter2[position, rotation]()
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
 		b.StopTimer()
-		q := query.Build(&world)
+		q := query.Query(&world)
 		b.StartTimer()
 		for q.Next() {
 			pos := q.Get1()
@@ -127,12 +127,12 @@ func runArcheQueryGeneric5C(b *testing.B, count int) {
 		world.Add(entity, id0, id1, id2, id3, id4)
 	}
 
-	query := generic.Query5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4]()
+	query := generic.NewFilter5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4]()
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
 		b.StopTimer()
-		q := query.Build(&world)
+		q := query.Query(&world)
 		b.StartTimer()
 		for q.Next() {
 			t1 := q.Get1()
diff --git a/ecs/query_test.go b/ecs/query_test.go
index 1472bea..1b7fdf2 100644
--- a/ecs/query_test.go
+++ b/ecs/query_test.go
@@ -100,6 +100,6 @@ func TestInterface(t *testing.T) {
 	rotID := ComponentID[rotation](&w)
 
 	q := w.Query(All(posID, rotID))
-	var q2 EntityIter = &q
+	var q2 QueryIter = &q
 	_ = q2
 }
diff --git a/ecs/world_test.go b/ecs/world_test.go
index 39a27de..c434bdb 100644
--- a/ecs/world_test.go
+++ b/ecs/world_test.go
@@ -398,7 +398,7 @@ func TestTypeSizes(t *testing.T) {
 	printTypeSizeName[pagedArr32[archetype]]("PagedArr32")
 	printTypeSize[archetype]()
 	printTypeSize[storage]()
-	printTypeSize[EntityIter]()
+	printTypeSize[QueryIter]()
 	printTypeSize[archetypeIter]()
 }
 
diff --git a/filter/filter_test.go b/filter/filter_test.go
index 827a2b5..ee50c7d 100644
--- a/filter/filter_test.go
+++ b/filter/filter_test.go
@@ -195,7 +195,7 @@ func TestInterface(t *testing.T) {
 	posID := ecs.ComponentID[position](&w)
 
 	f := w.Query(All(posID))
-	var f2 ecs.EntityIter = &f
+	var f2 ecs.QueryIter = &f
 	_ = f2
 }
 
diff --git a/generic/query_generic_test.go b/generic/query_test.go
similarity index 85%
rename from generic/query_generic_test.go
rename to generic/query_test.go
index 682b068..684043f 100644
--- a/generic/query_generic_test.go
+++ b/generic/query_test.go
@@ -30,42 +30,42 @@ func TestQueryOptionalNot(t *testing.T) {
 	w.Assign(e2, 2, &testStruct2{1, 1})
 	w.Assign(e2, 9, &testStruct9{})
 
-	query2 := Query2[testStruct0, testStruct1]().Build(&w)
+	query2 := NewFilter2[testStruct0, testStruct1]().Query(&w)
 	cnt := 0
 	for query2.Next() {
 		cnt++
 	}
 	assert.Equal(t, 3, cnt)
 
-	query2 = Query2[testStruct0, testStruct1]().Without(Mask1[testStruct9]()).Build(&w)
+	query2 = NewFilter2[testStruct0, testStruct1]().Without(Mask1[testStruct9]()).Query(&w)
 	cnt = 0
 	for query2.Next() {
 		cnt++
 	}
 	assert.Equal(t, 2, cnt)
 
-	query2 = Query2[testStruct0, testStruct1]().Without(Mask2[testStruct8, testStruct9]()).Build(&w)
+	query2 = NewFilter2[testStruct0, testStruct1]().Without(Mask2[testStruct8, testStruct9]()).Query(&w)
 	cnt = 0
 	for query2.Next() {
 		cnt++
 	}
 	assert.Equal(t, 1, cnt)
 
-	query2 = Query2[testStruct0, testStruct1]().With(Mask1[testStruct2]()).Without(Mask1[testStruct9]()).Build(&w)
+	query2 = NewFilter2[testStruct0, testStruct1]().With(Mask1[testStruct2]()).Without(Mask1[testStruct9]()).Query(&w)
 	cnt = 0
 	for query2.Next() {
 		cnt++
 	}
 	assert.Equal(t, 1, cnt)
 
-	query3 := Query3[testStruct0, testStruct1, testStruct9]().Build(&w)
+	query3 := NewFilter3[testStruct0, testStruct1, testStruct9]().Query(&w)
 	cnt = 0
 	for query3.Next() {
 		cnt++
 	}
 	assert.Equal(t, 1, cnt)
 
-	query3 = Query3[testStruct0, testStruct1, testStruct9]().Optional(Mask1[testStruct9]()).Build(&w)
+	query3 = NewFilter3[testStruct0, testStruct1, testStruct9]().Optional(Mask1[testStruct9]()).Query(&w)
 	cnt = 0
 	for query3.Next() {
 		cnt++
@@ -91,16 +91,19 @@ func TestQuery0(t *testing.T) {
 	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query :=
-		Query0().
+	filter :=
+		NewFilter0().
+			Optional(Mask1[testStruct1]()).
 			With(Mask1[testStruct8]()).
-			Without(Mask1[testStruct9]()).
-			Build(&w)
+			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(8).Without(9), filter.Filter(&w))
+	query := filter.Query(&w)
 	for query.Next() {
 		cnt++
 	}
 	assert.Equal(t, 1, cnt)
+
 }
 
 func TestQuery1(t *testing.T) {
@@ -122,15 +125,18 @@ func TestQuery1(t *testing.T) {
 	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query :=
-		Query1[testStruct0]().
+	filter :=
+		NewFilter1[testStruct0]().
 			Optional(Mask1[testStruct9]()).
 			With(Mask1[testStruct8]()).
-			Without(Mask1[testStruct9]()).
-			Build(&w)
+			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(0, 8).Without(9), filter.Filter(&w))
+	query := filter.Query(&w)
 	for query.Next() {
 		c0 := query.Get1()
+		c02 := query.GetAll()
+		assert.Equal(t, c0, c02)
 		assert.Equal(t, cnt+1, int(c0.val))
 		cnt++
 	}
@@ -158,15 +164,17 @@ func TestQuery2(t *testing.T) {
 
 	w.Assign(e2, 9, &testStruct9{})
 
-	query :=
-		Query2[testStruct0, testStruct1]().
+	filter :=
+		NewFilter2[testStruct0, testStruct1]().
 			Optional(Mask1[testStruct1]()).
 			With(Mask1[testStruct8]()).
 			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(0, 8).Without(9), filter.Filter(&w))
+
 	for i := 0; i < 10; i++ {
 		cnt := 0
-		q := query.Build(&w)
+		q := filter.Query(&w)
 		for q.Next() {
 			c1 := q.Get1()
 			c2 := q.Get2()
@@ -208,13 +216,14 @@ func TestQuery3(t *testing.T) {
 	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query :=
-		Query3[testStruct0, testStruct1, testStruct2]().
+	filter :=
+		NewFilter3[testStruct0, testStruct1, testStruct2]().
 			Optional(Mask1[testStruct1]()).
 			With(Mask1[testStruct8]()).
-			Without(Mask1[testStruct9]()).
-			Build(&w)
+			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(0, 2, 8).Without(9), filter.Filter(&w))
+	query := filter.Query(&w)
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -262,13 +271,14 @@ func TestQuery4(t *testing.T) {
 	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query :=
-		Query4[testStruct0, testStruct1, testStruct2, testStruct3]().
+	filter :=
+		NewFilter4[testStruct0, testStruct1, testStruct2, testStruct3]().
 			Optional(Mask1[testStruct1]()).
 			With(Mask1[testStruct8]()).
-			Without(Mask1[testStruct9]()).
-			Build(&w)
+			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(0, 2, 3, 8).Without(9), filter.Filter(&w))
+	query := filter.Query(&w)
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -323,13 +333,14 @@ func TestQuery5(t *testing.T) {
 	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query :=
-		Query5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4]().
+	filter :=
+		NewFilter5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4]().
 			Optional(Mask1[testStruct1]()).
 			With(Mask1[testStruct8]()).
-			Without(Mask1[testStruct9]()).
-			Build(&w)
+			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(0, 2, 3, 4, 8).Without(9), filter.Filter(&w))
+	query := filter.Query(&w)
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -391,13 +402,14 @@ func TestQuery6(t *testing.T) {
 	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query :=
-		Query6[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4, testStruct5]().
+	filter :=
+		NewFilter6[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4, testStruct5]().
 			Optional(Mask1[testStruct1]()).
 			With(Mask1[testStruct8]()).
-			Without(Mask1[testStruct9]()).
-			Build(&w)
+			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(0, 2, 3, 4, 5, 8).Without(9), filter.Filter(&w))
+	query := filter.Query(&w)
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -466,16 +478,17 @@ func TestQuery7(t *testing.T) {
 	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query :=
-		Query7[
+	filter :=
+		NewFilter7[
 			testStruct0, testStruct1, testStruct2, testStruct3, testStruct4,
 			testStruct5, testStruct6,
 		]().
 			Optional(Mask1[testStruct1]()).
 			With(Mask1[testStruct8]()).
-			Without(Mask1[testStruct9]()).
-			Build(&w)
+			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(0, 2, 3, 4, 5, 6, 8).Without(9), filter.Filter(&w))
+	query := filter.Query(&w)
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -543,16 +556,17 @@ func TestQuery8(t *testing.T) {
 	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query :=
-		Query8[
+	filter :=
+		NewFilter8[
 			testStruct0, testStruct1, testStruct2, testStruct3,
 			testStruct4, testStruct5, testStruct6, testStruct7,
 		]().
 			Optional(Mask1[testStruct1]()).
 			With(Mask1[testStruct8]()).
-			Without(Mask1[testStruct9]()).
-			Build(&w)
+			Without(Mask1[testStruct9]())
 
+	assert.Equal(t, ecs.All(0, 2, 3, 4, 5, 6, 7, 8).Without(9), filter.Filter(&w))
+	query := filter.Query(&w)
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -686,9 +700,9 @@ func TestQueryGeneric(t *testing.T) {
 		entity := world.NewEntity()
 		world.Add(entity, posID, rotID)
 	}
-	query := Query2[testStruct2, testStruct3]()
+	query := NewFilter2[testStruct2, testStruct3]()
 
-	q := query.Build(&world)
+	q := query.Query(&world)
 	cnt := 0
 	for q.Next() {
 		s1 := q.Get1()