Repository: mlange-42/arche
Pull request #53 (2023-02-11 14:43:27)

Queries with exclusion


Patch:
diff --git a/ecs/bitmask.go b/ecs/bitmask.go
index abf8da8..187a8b6 100644
--- a/ecs/bitmask.go
+++ b/ecs/bitmask.go
@@ -52,6 +52,11 @@ func (e bitMask) Contains(other bitMask) bool {
 	return e&other == other
 }
 
+// ContainsAny reports if any bit of other mask is a subset of this mask.
+func (e bitMask) ContainsAny(other bitMask) bool {
+	return e&other != 0
+}
+
 // TotalBitsSet returns how many bits are set in this mask.
 func (e bitMask) TotalBitsSet() uint {
 	var count uint
diff --git a/ecs/query.go b/ecs/query.go
index 61df0f8..2bffb0f 100644
--- a/ecs/query.go
+++ b/ecs/query.go
@@ -19,6 +19,7 @@ import (
 type Query struct {
 	world     *World
 	mask      bitMask
+	exclude   bitMask
 	archetype archetypeIter
 	index     int
 	done      bool
@@ -26,21 +27,33 @@ type Query struct {
 }
 
 // newQuery creates a new Query
-func newQuery(world *World, mask bitMask, lockBit uint8) Query {
+func newQuery(world *World, mask, exclude bitMask, lockBit uint8) Query {
 	return Query{
 		world:   world,
 		mask:    mask,
+		exclude: exclude,
 		index:   -1,
 		lockBit: lockBit,
 	}
 }
 
+// Not excludes components from the query.
+// Entities with these components will be skipped.
+//
+// # Example:
+//
+//	query := world.Query(idA, isB).Not(idC, isD)
+func (q Query) Not(comps ...ID) Query {
+	q.exclude = newMask(comps...)
+	return q
+}
+
 // Next proceeds to the next [Entity] in the Query.
 func (q *Query) Next() bool {
 	if q.archetype.Next() {
 		return true
 	}
-	i, a, ok := q.world.nextArchetype(q.mask, q.index)
+	i, a, ok := q.world.nextArchetype(q.mask, q.exclude, q.index)
 	q.index = i
 	if ok {
 		q.archetype = a
diff --git a/ecs/query_generic.go b/ecs/query_generic.go
index dfcfab4..ef4b364 100644
--- a/ecs/query_generic.go
+++ b/ecs/query_generic.go
@@ -1,5 +1,34 @@
 package ecs
 
+// Mask is a mask for a combination of components.
+type Mask struct {
+	mask bitMask
+}
+
+// Q0 is a generic query for no components.
+//
+// Create one with [Query0]
+type Q0 struct {
+	Query
+}
+
+// Query0 creates a generic query for no components.
+//
+// See also [World.Query].
+func Query0(w *World) Q0 {
+	return Q0{
+		Query: w.query(0, 0),
+	}
+}
+
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q0) Not(mask Mask) Q0 {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // Q1 is a generic query for one component.
 //
 // Create one with [Query1]
@@ -19,6 +48,14 @@ func Query1[A any](w *World) Q1[A] {
 	}
 }
 
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q1[A]) Not(mask Mask) Q1[A] {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // Get1 returns the first queried component for the current query position
 func (q *Q1[A]) Get1() *A {
 	return (*A)(q.Query.Get(q.id))
@@ -45,6 +82,14 @@ func Query2[A any, B any](w *World) Q2[A, B] {
 	}
 }
 
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q2[A, B]) Not(mask Mask) Q2[A, B] {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // 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]))
@@ -81,6 +126,14 @@ func Query3[A any, B any, C any](w *World) Q3[A, B, C] {
 	}
 }
 
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q3[A, B, C]) Not(mask Mask) Q3[A, B, C] {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // 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]))
@@ -122,6 +175,14 @@ func Query4[A any, B any, C any, D any](w *World) Q4[A, B, C, D] {
 	}
 }
 
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q4[A, B, C, D]) Not(mask Mask) Q4[A, B, C, D] {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // 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])),
@@ -171,6 +232,14 @@ func Query5[A any, B any, C any, D any, E any](w *World) Q5[A, B, C, D, E] {
 	}
 }
 
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q5[A, B, C, D, E]) Not(mask Mask) Q5[A, B, C, D, E] {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // 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])),
@@ -229,6 +298,14 @@ func Query6[A any, B any, C any, D any, E any, F any](w *World) Q6[A, B, C, D, E
 	}
 }
 
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q6[A, B, C, D, E, F]) Not(mask Mask) Q6[A, B, C, D, E, F] {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // 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])),
@@ -305,6 +382,14 @@ func (q *Q7[A, B, C, D, E, F, G]) GetAll() (*A, *B, *C, *D, *E, *F, *G) {
 		(*G)(q.Query.Get(q.ids[6]))
 }
 
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q7[A, B, C, D, E, F, G]) Not(mask Mask) Q7[A, B, C, D, E, F, G] {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // 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]))
@@ -365,6 +450,14 @@ func Query8[A any, B any, C any, D any, E any, F any, G any, H any](w *World) Q8
 	}
 }
 
+// Not excludes entities with the given components from the query.
+//
+// Create the required mask with [Mask1], [Mask2], etc.
+func (q Q8[A, B, C, D, E, F, G, H]) Not(mask Mask) Q8[A, B, C, D, E, F, G, H] {
+	q.exclude = q.exclude | mask.mask
+	return q
+}
+
 // 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])),
@@ -416,3 +509,64 @@ func (q *Q8[A, B, C, D, E, F, G, H]) Get7() *G {
 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 [Mask] for one component type.
+func Mask1[A any](w *World) Mask {
+	return Mask{newMask(ComponentID[A](w))}
+}
+
+// Mask2 creates a component [Mask] for two component types.
+func Mask2[A any, B any](w *World) Mask {
+	return Mask{newMask(
+		ComponentID[A](w), ComponentID[B](w),
+	)}
+}
+
+// Mask3 creates a component [Mask] for three component types.
+func Mask3[A any, B any, C any](w *World) Mask {
+	return Mask{newMask(
+		ComponentID[A](w), ComponentID[B](w), ComponentID[C](w),
+	)}
+}
+
+// Mask4 creates a component [Mask] for four component types.
+func Mask4[A any, B any, C any, D any](w *World) Mask {
+	return Mask{newMask(
+		ComponentID[A](w), ComponentID[B](w), ComponentID[C](w),
+		ComponentID[D](w),
+	)}
+}
+
+// Mask5 creates a component [Mask] for five component types.
+func Mask5[A any, B any, C any, D any, E any](w *World) Mask {
+	return Mask{newMask(
+		ComponentID[A](w), ComponentID[B](w), ComponentID[C](w),
+		ComponentID[D](w), ComponentID[E](w),
+	)}
+}
+
+// Mask6 creates a component [Mask] for six component types.
+func Mask6[A any, B any, C any, D any, E any, F any](w *World) Mask {
+	return Mask{newMask(
+		ComponentID[A](w), ComponentID[B](w), ComponentID[C](w),
+		ComponentID[D](w), ComponentID[E](w), ComponentID[F](w),
+	)}
+}
+
+// Mask7 creates a component [Mask] for seven component types.
+func Mask7[A any, B any, C any, D any, E any, F any, G any](w *World) Mask {
+	return Mask{newMask(
+		ComponentID[A](w), ComponentID[B](w), ComponentID[C](w),
+		ComponentID[D](w), ComponentID[E](w), ComponentID[F](w),
+		ComponentID[G](w),
+	)}
+}
+
+// Mask8 creates a component [Mask] for eight component types.
+func Mask8[A any, B any, C any, D any, E any, F any, G any, H any](w *World) Mask {
+	return Mask{newMask(
+		ComponentID[A](w), ComponentID[B](w), ComponentID[C](w),
+		ComponentID[D](w), ComponentID[E](w), ComponentID[F](w),
+		ComponentID[G](w), ComponentID[H](w),
+	)}
+}
diff --git a/ecs/world.go b/ecs/world.go
index 4303426..1ab8d7c 100644
--- a/ecs/world.go
+++ b/ecs/world.go
@@ -91,7 +91,13 @@ func (w *World) Query(comps ...ID) Query {
 	mask := newMask(comps...)
 	lock := w.bitPool.Get()
 	w.locks.Set(ID(lock), true)
-	return newQuery(w, mask, lock)
+	return newQuery(w, mask, 0, lock)
+}
+
+func (w *World) query(mask, exclude bitMask) Query {
+	lock := w.bitPool.Get()
+	w.locks.Set(ID(lock), true)
+	return newQuery(w, mask, exclude, lock)
 }
 
 // Alive reports whether an entity is still alive.
@@ -337,14 +343,14 @@ func (w *World) componentID(tp reflect.Type) ID {
 	return w.registry.ComponentID(tp)
 }
 
-func (w *World) nextArchetype(mask bitMask, index int) (int, archetypeIter, bool) {
+func (w *World) nextArchetype(mask, exclude bitMask, index int) (int, archetypeIter, bool) {
 	len := w.archetypes.Len()
 	if index >= len {
 		panic("exceeded end of query")
 	}
 	for i := index + 1; i < len; i++ {
 		a := w.archetypes.Get(i)
-		if a.Len() > 0 && a.mask.Contains(mask) {
+		if a.Len() > 0 && a.mask.Contains(mask) && !a.mask.ContainsAny(exclude) {
 			return i, newArchetypeIter(a), true
 		}
 	}
diff --git a/go.work b/go.work
index 5580074..73bdcd7 100644
--- a/go.work
+++ b/go.work
@@ -1,7 +1,7 @@
 go 1.20
 
 use (
-    .
-    ./benchmark
-    ./profile
+	.
+	./benchmark
+	./profile
 )


Test patch:
diff --git a/benchmark/iterate/arche_test.go b/benchmark/iterate/arche_test.go
index dd23ac3..561643f 100644
--- a/benchmark/iterate/arche_test.go
+++ b/benchmark/iterate/arche_test.go
@@ -30,6 +30,35 @@ func runArcheQuery(b *testing.B, count int) {
 	}
 }
 
+func runArcheQueryPointerHeap(b *testing.B, count int) {
+	b.StopTimer()
+	world := ecs.NewWorld()
+
+	posID := ecs.ComponentID[position](&world)
+	rotID := ecs.ComponentID[rotation](&world)
+
+	for i := 0; i < count; i++ {
+		entity := world.NewEntity()
+		world.Add(entity, posID, rotID)
+	}
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		b.StopTimer()
+		query := createQuery(&world, posID, rotID)
+		b.StartTimer()
+		for query.Next() {
+			pos := (*position)(query.Get(posID))
+			_ = pos
+		}
+	}
+}
+
+func createQuery(w *ecs.World, comps ...ecs.ID) *ecs.Query {
+	q := w.Query(comps...)
+	return &q
+}
+
 func runArcheQueryGeneric(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
@@ -211,6 +240,18 @@ func BenchmarkArcheIterQueryID_100_000(b *testing.B) {
 	runArcheQuery(b, 100000)
 }
 
+func BenchmarkArcheIterQueryIDPointer_1_000(b *testing.B) {
+	runArcheQueryPointerHeap(b, 1000)
+}
+
+func BenchmarkArcheIterQueryIDPointer_10_000(b *testing.B) {
+	runArcheQueryPointerHeap(b, 10000)
+}
+
+func BenchmarkArcheIterQueryIDPointer_100_000(b *testing.B) {
+	runArcheQueryPointerHeap(b, 100000)
+}
+
 func BenchmarkArcheIterQueryGeneric_1_000(b *testing.B) {
 	runArcheQueryGeneric(b, 1000)
 }
diff --git a/ecs/bitmask_test.go b/ecs/bitmask_test.go
index dba25b6..2bfe965 100644
--- a/ecs/bitmask_test.go
+++ b/ecs/bitmask_test.go
@@ -33,4 +33,11 @@ func TestBitMask(t *testing.T) {
 
 	mask.Reset()
 	assert.Equal(t, uint(0), mask.TotalBitsSet())
+
+	mask = newMask(ID(1), ID(2), ID(13), ID(27))
+	other1 = newMask(ID(1), ID(32))
+	other2 = newMask(ID(0), ID(32))
+
+	assert.True(t, mask.ContainsAny(other1))
+	assert.False(t, mask.ContainsAny(other2))
 }
diff --git a/ecs/query_generic_test.go b/ecs/query_generic_test.go
index a347b4a..12daf4f 100644
--- a/ecs/query_generic_test.go
+++ b/ecs/query_generic_test.go
@@ -6,19 +6,50 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func TestQuery0(t *testing.T) {
+	w := NewWorld()
+
+	registerAll(&w)
+
+	e0 := w.NewEntity()
+	e1 := w.NewEntity()
+	e2 := w.NewEntity()
+
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
+	w.Assign(e2, 0, &testStruct0{2})
+	w.Assign(e2, 9, &testStruct9{})
+
+	cnt := 0
+	query :=
+		Query0(&w).
+			Not(Mask1[testStruct9](&w))
+
+	for query.Next() {
+		cnt++
+	}
+	assert.Equal(t, 2, cnt)
+}
+
 func TestQuery1(t *testing.T) {
 	w := NewWorld()
 
-	id0 := ComponentID[testStruct0](&w)
+	registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
+	e2 := w.NewEntity()
 
-	w.Assign(e0, id0, &testStruct0{1})
-	w.Assign(e1, id0, &testStruct0{2})
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
+	w.Assign(e2, 0, &testStruct0{0})
+	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query := Query1[testStruct0](&w)
+	query :=
+		Query1[testStruct0](&w).
+			Not(Mask1[testStruct9](&w))
+
 	for query.Next() {
 		c0 := query.Get1()
 		assert.Equal(t, cnt+1, int(c0.val))
@@ -30,20 +61,27 @@ func TestQuery1(t *testing.T) {
 func TestQuery2(t *testing.T) {
 	w := NewWorld()
 
-	id0 := ComponentID[testStruct0](&w)
-	id1 := ComponentID[testStruct1](&w)
+	registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
+	e2 := w.NewEntity()
+
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
+	w.Assign(e2, 0, &testStruct0{3})
 
-	w.Assign(e0, id0, &testStruct0{1})
-	w.Assign(e1, id0, &testStruct0{2})
+	w.Assign(e0, 1, &testStruct1{2})
+	w.Assign(e1, 1, &testStruct1{3})
+	w.Assign(e2, 1, &testStruct1{4})
 
-	w.Assign(e0, id1, &testStruct1{2})
-	w.Assign(e1, id1, &testStruct1{3})
+	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query := Query2[testStruct0, testStruct1](&w)
+	query :=
+		Query2[testStruct0, testStruct1](&w).
+			Not(Mask1[testStruct9](&w))
+
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -61,24 +99,31 @@ func TestQuery2(t *testing.T) {
 func TestQuery3(t *testing.T) {
 	w := NewWorld()
 
-	id0 := ComponentID[testStruct0](&w)
-	id1 := ComponentID[testStruct1](&w)
-	id2 := ComponentID[testStruct2](&w)
+	registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
+	e2 := w.NewEntity()
+
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
+	w.Assign(e2, 0, &testStruct0{3})
 
-	w.Assign(e0, id0, &testStruct0{1})
-	w.Assign(e1, id0, &testStruct0{2})
+	w.Assign(e0, 1, &testStruct1{2})
+	w.Assign(e1, 1, &testStruct1{3})
+	w.Assign(e2, 1, &testStruct1{4})
 
-	w.Assign(e0, id1, &testStruct1{2})
-	w.Assign(e1, id1, &testStruct1{3})
+	w.Assign(e0, 2, &testStruct2{3})
+	w.Assign(e1, 2, &testStruct2{4})
+	w.Assign(e2, 2, &testStruct2{5})
 
-	w.Assign(e0, id2, &testStruct2{3})
-	w.Assign(e1, id2, &testStruct2{4})
+	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query := Query3[testStruct0, testStruct1, testStruct2](&w)
+	query :=
+		Query3[testStruct0, testStruct1, testStruct2](&w).
+			Not(Mask1[testStruct9](&w))
+
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -99,28 +144,34 @@ func TestQuery3(t *testing.T) {
 func TestQuery4(t *testing.T) {
 	w := NewWorld()
 
-	id0 := ComponentID[testStruct0](&w)
-	id1 := ComponentID[testStruct1](&w)
-	id2 := ComponentID[testStruct2](&w)
-	id3 := ComponentID[testStruct3](&w)
+	registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
+	e2 := w.NewEntity()
+
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
+	w.Assign(e2, 0, &testStruct0{2})
 
-	w.Assign(e0, id0, &testStruct0{1})
-	w.Assign(e1, id0, &testStruct0{2})
+	w.Assign(e0, 1, &testStruct1{2})
+	w.Assign(e1, 1, &testStruct1{3})
+	w.Assign(e2, 1, &testStruct1{3})
 
-	w.Assign(e0, id1, &testStruct1{2})
-	w.Assign(e1, id1, &testStruct1{3})
+	w.Assign(e0, 2, &testStruct2{3})
+	w.Assign(e1, 2, &testStruct2{4})
+	w.Assign(e2, 2, &testStruct2{4})
 
-	w.Assign(e0, id2, &testStruct2{3})
-	w.Assign(e1, id2, &testStruct2{4})
+	w.Assign(e0, 3, &testStruct3{4})
+	w.Assign(e1, 3, &testStruct3{5})
+	w.Assign(e2, 3, &testStruct3{5})
 
-	w.Assign(e0, id3, &testStruct3{4})
-	w.Assign(e1, id3, &testStruct3{5})
+	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query := Query4[testStruct0, testStruct1, testStruct2, testStruct3](&w)
+	query :=
+		Query4[testStruct0, testStruct1, testStruct2, testStruct3](&w).
+			Not(Mask1[testStruct9](&w))
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -144,32 +195,38 @@ func TestQuery4(t *testing.T) {
 func TestQuery5(t *testing.T) {
 	w := NewWorld()
 
-	id0 := ComponentID[testStruct0](&w)
-	id1 := ComponentID[testStruct1](&w)
-	id2 := ComponentID[testStruct2](&w)
-	id3 := ComponentID[testStruct3](&w)
-	id4 := ComponentID[testStruct4](&w)
+	registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
+	e2 := w.NewEntity()
+
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
+	w.Assign(e2, 0, &testStruct0{2})
 
-	w.Assign(e0, id0, &testStruct0{1})
-	w.Assign(e1, id0, &testStruct0{2})
+	w.Assign(e0, 1, &testStruct1{2})
+	w.Assign(e1, 1, &testStruct1{3})
+	w.Assign(e2, 1, &testStruct1{3})
 
-	w.Assign(e0, id1, &testStruct1{2})
-	w.Assign(e1, id1, &testStruct1{3})
+	w.Assign(e0, 2, &testStruct2{3})
+	w.Assign(e1, 2, &testStruct2{4})
+	w.Assign(e2, 2, &testStruct2{4})
 
-	w.Assign(e0, id2, &testStruct2{3})
-	w.Assign(e1, id2, &testStruct2{4})
+	w.Assign(e0, 3, &testStruct3{4})
+	w.Assign(e1, 3, &testStruct3{5})
+	w.Assign(e2, 3, &testStruct3{5})
 
-	w.Assign(e0, id3, &testStruct3{4})
-	w.Assign(e1, id3, &testStruct3{5})
+	w.Assign(e0, 4, &testStruct4{5})
+	w.Assign(e1, 4, &testStruct4{6})
+	w.Assign(e2, 4, &testStruct4{6})
 
-	w.Assign(e0, id4, &testStruct4{5})
-	w.Assign(e1, id4, &testStruct4{6})
+	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query := Query5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4](&w)
+	query :=
+		Query5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4](&w).
+			Not(Mask1[testStruct9](&w))
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -196,36 +253,42 @@ func TestQuery5(t *testing.T) {
 func TestQuery6(t *testing.T) {
 	w := NewWorld()
 
-	id0 := ComponentID[testStruct0](&w)
-	id1 := ComponentID[testStruct1](&w)
-	id2 := ComponentID[testStruct2](&w)
-	id3 := ComponentID[testStruct3](&w)
-	id4 := ComponentID[testStruct4](&w)
-	id5 := ComponentID[testStruct5](&w)
+	registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
+	e2 := w.NewEntity()
 
-	w.Assign(e0, id0, &testStruct0{1})
-	w.Assign(e1, id0, &testStruct0{2})
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
+	w.Assign(e2, 0, &testStruct0{2})
 
-	w.Assign(e0, id1, &testStruct1{2})
-	w.Assign(e1, id1, &testStruct1{3})
+	w.Assign(e0, 1, &testStruct1{2})
+	w.Assign(e1, 1, &testStruct1{3})
+	w.Assign(e2, 1, &testStruct1{3})
 
-	w.Assign(e0, id2, &testStruct2{3})
-	w.Assign(e1, id2, &testStruct2{4})
+	w.Assign(e0, 2, &testStruct2{3})
+	w.Assign(e1, 2, &testStruct2{4})
+	w.Assign(e2, 2, &testStruct2{4})
 
-	w.Assign(e0, id3, &testStruct3{4})
-	w.Assign(e1, id3, &testStruct3{5})
+	w.Assign(e0, 3, &testStruct3{4})
+	w.Assign(e1, 3, &testStruct3{5})
+	w.Assign(e2, 3, &testStruct3{5})
 
-	w.Assign(e0, id4, &testStruct4{5})
-	w.Assign(e1, id4, &testStruct4{6})
+	w.Assign(e0, 4, &testStruct4{5})
+	w.Assign(e1, 4, &testStruct4{6})
+	w.Assign(e2, 4, &testStruct4{6})
 
-	w.Assign(e0, id5, &testStruct5{6})
-	w.Assign(e1, id5, &testStruct5{7})
+	w.Assign(e0, 5, &testStruct5{6})
+	w.Assign(e1, 5, &testStruct5{7})
+	w.Assign(e2, 5, &testStruct5{7})
+
+	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query := Query6[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4, testStruct5](&w)
+	query :=
+		Query6[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4, testStruct5](&w).
+			Not(Mask1[testStruct9](&w))
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -255,40 +318,46 @@ func TestQuery6(t *testing.T) {
 func TestQuery7(t *testing.T) {
 	w := NewWorld()
 
-	id0 := ComponentID[testStruct0](&w)
-	id1 := ComponentID[testStruct1](&w)
-	id2 := ComponentID[testStruct2](&w)
-	id3 := ComponentID[testStruct3](&w)
-	id4 := ComponentID[testStruct4](&w)
-	id5 := ComponentID[testStruct5](&w)
-	id6 := ComponentID[testStruct6](&w)
+	registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
+	e2 := w.NewEntity()
+
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
+	w.Assign(e2, 0, &testStruct0{2})
 
-	w.Assign(e0, id0, &testStruct0{1})
-	w.Assign(e1, id0, &testStruct0{2})
+	w.Assign(e0, 1, &testStruct1{2})
+	w.Assign(e1, 1, &testStruct1{3})
+	w.Assign(e2, 1, &testStruct1{3})
 
-	w.Assign(e0, id1, &testStruct1{2})
-	w.Assign(e1, id1, &testStruct1{3})
+	w.Assign(e0, 2, &testStruct2{3})
+	w.Assign(e1, 2, &testStruct2{4})
+	w.Assign(e2, 2, &testStruct2{4})
 
-	w.Assign(e0, id2, &testStruct2{3})
-	w.Assign(e1, id2, &testStruct2{4})
+	w.Assign(e0, 3, &testStruct3{4})
+	w.Assign(e1, 3, &testStruct3{5})
+	w.Assign(e2, 3, &testStruct3{5})
 
-	w.Assign(e0, id3, &testStruct3{4})
-	w.Assign(e1, id3, &testStruct3{5})
+	w.Assign(e0, 4, &testStruct4{5})
+	w.Assign(e1, 4, &testStruct4{6})
+	w.Assign(e2, 4, &testStruct4{6})
 
-	w.Assign(e0, id4, &testStruct4{5})
-	w.Assign(e1, id4, &testStruct4{6})
+	w.Assign(e0, 5, &testStruct5{6})
+	w.Assign(e1, 5, &testStruct5{7})
+	w.Assign(e2, 5, &testStruct5{7})
 
-	w.Assign(e0, id5, &testStruct5{6})
-	w.Assign(e1, id5, &testStruct5{7})
+	w.Assign(e0, 6, &testStruct5{7})
+	w.Assign(e1, 6, &testStruct5{8})
+	w.Assign(e2, 6, &testStruct5{8})
 
-	w.Assign(e0, id6, &testStruct5{7})
-	w.Assign(e1, id6, &testStruct5{8})
+	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query := Query7[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4, testStruct5, testStruct6](&w)
+	query :=
+		Query7[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4, testStruct5, testStruct6](&w).
+			Not(Mask1[testStruct9](&w))
 	for query.Next() {
 		c1 := query.Get1()
 		c2 := query.Get2()
@@ -321,46 +390,44 @@ func TestQuery7(t *testing.T) {
 func TestQuery8(t *testing.T) {
 	w := NewWorld()
 
-	id0 := ComponentID[testStruct0](&w)
-	id1 := ComponentID[testStruct1](&w)
-	id2 := ComponentID[testStruct2](&w)
-	id3 := ComponentID[testStruct3](&w)
-	id4 := ComponentID[testStruct4](&w)
-	id5 := ComponentID[testStruct5](&w)
-	id6 := ComponentID[testStruct6](&w)
-	id7 := ComponentID[testStruct7](&w)
+	registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
+	e2 := w.NewEntity()
 
-	w.Assign(e0, id0, &testStruct0{1})
-	w.Assign(e1, id0, &testStruct0{2})
+	w.Assign(e0, 0, &testStruct0{1})
+	w.Assign(e1, 0, &testStruct0{2})
 
-	w.Assign(e0, id1, &testStruct1{2})
-	w.Assign(e1, id1, &testStruct1{3})
+	w.Assign(e0, 1, &testStruct1{2})
+	w.Assign(e1, 1, &testStruct1{3})
 
-	w.Assign(e0, id2, &testStruct2{3})
-	w.Assign(e1, id2, &testStruct2{4})
+	w.Assign(e0, 2, &testStruct2{3})
+	w.Assign(e1, 2, &testStruct2{4})
 
-	w.Assign(e0, id3, &testStruct3{4})
-	w.Assign(e1, id3, &testStruct3{5})
+	w.Assign(e0, 3, &testStruct3{4})
+	w.Assign(e1, 3, &testStruct3{5})
 
-	w.Assign(e0, id4, &testStruct4{5})
-	w.Assign(e1, id4, &testStruct4{6})
+	w.Assign(e0, 4, &testStruct4{5})
+	w.Assign(e1, 4, &testStruct4{6})
 
-	w.Assign(e0, id5, &testStruct5{6})
-	w.Assign(e1, id5, &testStruct5{7})
+	w.Assign(e0, 5, &testStruct5{6})
+	w.Assign(e1, 5, &testStruct5{7})
 
-	w.Assign(e0, id6, &testStruct5{7})
-	w.Assign(e1, id6, &testStruct5{8})
+	w.Assign(e0, 6, &testStruct5{7})
+	w.Assign(e1, 6, &testStruct5{8})
 
-	w.Assign(e0, id7, &testStruct5{8})
-	w.Assign(e1, id7, &testStruct5{9})
+	w.Assign(e0, 7, &testStruct5{8})
+	w.Assign(e1, 7, &testStruct5{9})
+
+	w.Assign(e2, 9, &testStruct9{})
 
 	cnt := 0
-	query := Query8[
-		testStruct0, testStruct1, testStruct2, testStruct3,
-		testStruct4, testStruct5, testStruct6, testStruct7](&w)
+	query :=
+		Query8[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7](&w).
+			Not(Mask1[testStruct9](&w))
 
 	for query.Next() {
 		c1 := query.Get1()
@@ -393,3 +460,51 @@ func TestQuery8(t *testing.T) {
 	}
 	assert.Equal(t, 2, cnt)
 }
+
+func TestGenericMask(t *testing.T) {
+	w := NewWorld()
+
+	assert.Equal(t,
+		Mask1[testStruct0](&w).mask,
+		newMask(0),
+	)
+
+	assert.Equal(t,
+		Mask2[testStruct0, testStruct1](&w).mask,
+		newMask(0, 1),
+	)
+
+	assert.Equal(t,
+		Mask3[testStruct0, testStruct1, testStruct2](&w).mask,
+		newMask(0, 1, 2),
+	)
+
+	assert.Equal(t,
+		Mask4[testStruct0, testStruct1, testStruct2, testStruct3](&w).mask,
+		newMask(0, 1, 2, 3),
+	)
+
+	assert.Equal(t,
+		Mask5[testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4](&w).mask,
+		newMask(0, 1, 2, 3, 4),
+	)
+
+	assert.Equal(t,
+		Mask6[testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5](&w).mask,
+		newMask(0, 1, 2, 3, 4, 5),
+	)
+
+	assert.Equal(t,
+		Mask7[testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6](&w).mask,
+		newMask(0, 1, 2, 3, 4, 5, 6),
+	)
+
+	assert.Equal(t,
+		Mask8[testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7](&w).mask,
+		newMask(0, 1, 2, 3, 4, 5, 6, 7),
+	)
+}
diff --git a/ecs/query_test.go b/ecs/query_test.go
index ee96e6c..1f58e5f 100644
--- a/ecs/query_test.go
+++ b/ecs/query_test.go
@@ -62,4 +62,13 @@ func TestQuery(t *testing.T) {
 	assert.Equal(t, 4, cnt)
 
 	assert.Panics(t, func() { q.Next() })
+
+	q = w.Query(rotID).Not(posID)
+
+	cnt = 0
+	for q.Next() {
+		_ = q.Entity()
+		cnt++
+	}
+	assert.Equal(t, 2, cnt)
 }
diff --git a/ecs/test_types.go b/ecs/test_types.go
index ee26a7d..89231b1 100644
--- a/ecs/test_types.go
+++ b/ecs/test_types.go
@@ -26,3 +26,30 @@ type testStruct6 struct{ val int32 }
 type testStruct7 struct{ val int32 }
 type testStruct8 struct{ val int32 }
 type testStruct9 struct{ val int32 }
+
+func registerAll(w *World) []ID {
+	_ = testStruct0{1}
+	_ = testStruct1{1}
+	_ = testStruct2{1}
+	_ = testStruct3{1}
+	_ = testStruct4{1}
+	_ = testStruct5{1}
+	_ = testStruct6{1}
+	_ = testStruct7{1}
+	_ = testStruct8{1}
+	_ = testStruct9{1}
+
+	ids := make([]ID, 10)
+	ids[0] = ComponentID[testStruct0](w)
+	ids[1] = ComponentID[testStruct1](w)
+	ids[2] = ComponentID[testStruct2](w)
+	ids[3] = ComponentID[testStruct3](w)
+	ids[4] = ComponentID[testStruct4](w)
+	ids[5] = ComponentID[testStruct5](w)
+	ids[6] = ComponentID[testStruct6](w)
+	ids[7] = ComponentID[testStruct7](w)
+	ids[8] = ComponentID[testStruct8](w)
+	ids[9] = ComponentID[testStruct9](w)
+
+	return ids
+}