Repository: mlange-42/arche
Pull request #313 (2024-01-09 12:46:18)

Extend to a maximum of 256 component types
Rework `Mask` to hold an array of 4 x `uint64` to represent 256 bits. Everything else comes into place by itself.

Alternative to #312:

- `Filter.Match` takes a mask pointer as argument
- `Mask` is copied when using as a filter (is pointer receiver in #312)

Primarily avoids `All(ids...)` either returning a pointer, or requiring users to store the result (temporarily) to be able to get a pointer to it.

Performance remains unchanged, except that `MaskFilter` matches are about 3 times slower (i.e. filters with component inclusion and exclusion). Not sure why, as the mask functions used there are as fast as before.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 916d5a1..cdbaf14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 # Changelog
 
+## [[unpublished]](https://github.com/mlange-42/arche/compare/v0.9.0...main)
+
+### Highlights
+
+* Arche now supports 256 instead of 128 component types as well as resource types and engine locks (#313)
+
+### Breaking changes
+
+* `MaskTotalBits` changed from 128 to 256 (#313)
+* Removed `Mask.Lo` and `Mask.Hi`, internal mask representation is now private (#313)
+* `Filters.Matches(Mask)` became `Filters.Matches(*Mask)`; same for all `Filter` implementations (#313)  
+This change was necessary to get the same performance as before, despite the more heavyweight implementation of the now 256 bits `Mask`.
+
 ## [[v0.9.0]](https://github.com/mlange-42/arche/compare/v0.8.1...v0.9.0)
 
 ### Infrastructure
diff --git a/README.md b/README.md
index 83deff7..2a65b7d 100644
--- a/README.md
+++ b/README.md
@@ -151,7 +151,7 @@ Neither is silent failure, given the scientific background.
 
 ### Other limitations
 
-* The number of component types per `World` is limited to 128. This is mainly a performance decision.
+* The number of component types per `World` is limited to 256. This is mainly a performance decision.
 * The number of entities alive at any one time is limited to just under 5 billion (`uint32` ID).
 
 ## Benchmarks
diff --git a/ecs/archetype_node.go b/ecs/archetype_node.go
index e2da2cf..9b80d2e 100644
--- a/ecs/archetype_node.go
+++ b/ecs/archetype_node.go
@@ -84,7 +84,7 @@ func newArchNode(mask Mask, data *nodeData, relation int8, capacityIncrement int
 // Matches the archetype node against a filter.
 // Ignores the relation target.
 func (a *archNode) Matches(f Filter) bool {
-	return f.Matches(a.Mask)
+	return f.Matches(&a.Mask)
 }
 
 // Archetypes of the node.
diff --git a/ecs/bitmask.go b/ecs/bitmask.go
index 61ddebe..17fe980 100644
--- a/ecs/bitmask.go
+++ b/ecs/bitmask.go
@@ -5,17 +5,16 @@ import "math/bits"
 // MaskTotalBits is the size of Mask in bits.
 //
 // It is the maximum number of component types that may exist in any [World].
-const MaskTotalBits = 128
+const MaskTotalBits = 256
 const wordSize = 64
 
-// Mask is a 128 bit bitmask.
+// Mask is a 256 bit bitmask.
 // It is also a [Filter] for including certain components.
 //
 // Use [All] to create a mask for a list of component IDs.
 // A mask can be further specified using [Mask.Without] or [Mask.Exclusive].
 type Mask struct {
-	Lo uint64 // First 64 bits of the mask
-	Hi uint64 // Second 64 bits of the mask
+	bits [4]uint64 // 4x 64 bits of the mask
 }
 
 // All creates a new Mask from a list of IDs.
@@ -33,8 +32,8 @@ func All(ids ...ID) Mask {
 }
 
 // Matches the mask as filter against another mask.
-func (b Mask) Matches(bits Mask) bool {
-	return bits.Contains(b)
+func (b Mask) Matches(bits *Mask) bool {
+	return bits.Contains(&b)
 }
 
 // Without creates a [MaskFilter] which filters for including the mask's components,
@@ -59,61 +58,59 @@ func (b Mask) Exclusive() MaskFilter {
 //
 // Returns false for bit >= [MaskTotalBits].
 func (b *Mask) Get(bit ID) bool {
-	if bit < wordSize {
-		mask := uint64(1 << bit)
-		return b.Lo&mask == mask
-	}
-	mask := uint64(1 << (bit - wordSize))
-	return b.Hi&mask == mask
+	idx := bit / 64
+	offset := bit - (64 * idx)
+	mask := uint64(1 << offset)
+	return b.bits[idx]&mask == mask
 }
 
-// Set sets the state of bit at the given index.
+// Set sets the state of the bit at the given index.
 //
 // Has no effect for bit >= [MaskTotalBits].
 func (b *Mask) Set(bit ID, value bool) {
-	if bit < wordSize {
-		if value {
-			b.Lo |= uint64(1 << bit)
-		} else {
-			b.Lo &= uint64(^(1 << bit))
-		}
-	}
+	idx := bit / 64
+	offset := bit - (64 * idx)
 	if value {
-		b.Hi |= uint64(1 << (bit - wordSize))
+		b.bits[idx] |= (1 << offset)
 	} else {
-		b.Hi &= uint64(^(1 << (bit - wordSize)))
+		b.bits[idx] &= ^(1 << offset)
 	}
 }
 
 // Not returns the inversion of this mask.
 func (b *Mask) Not() Mask {
 	return Mask{
-		Lo: ^b.Lo,
-		Hi: ^b.Hi,
+		bits: [4]uint64{^b.bits[0], ^b.bits[1], ^b.bits[2], ^b.bits[3]},
 	}
 }
 
 // IsZero returns whether no bits are set in the mask.
 func (b *Mask) IsZero() bool {
-	return b.Lo == 0 && b.Hi == 0
+	return b.bits[0] == 0 && b.bits[1] == 0 && b.bits[2] == 0 && b.bits[3] == 0
 }
 
 // Reset the mask setting all bits to false.
 func (b *Mask) Reset() {
-	b.Lo, b.Lo = 0, 0
+	b.bits = [4]uint64{0, 0, 0, 0}
 }
 
 // Contains reports if the other mask is a subset of this mask.
-func (b *Mask) Contains(other Mask) bool {
-	return b.Lo&other.Lo == other.Lo && b.Hi&other.Hi == other.Hi
+func (b *Mask) Contains(other *Mask) bool {
+	return b.bits[0]&other.bits[0] == other.bits[0] &&
+		b.bits[1]&other.bits[1] == other.bits[1] &&
+		b.bits[2]&other.bits[2] == other.bits[2] &&
+		b.bits[3]&other.bits[3] == other.bits[3]
 }
 
 // ContainsAny reports if any bit of the other mask is in this mask.
-func (b *Mask) ContainsAny(other Mask) bool {
-	return b.Lo&other.Lo != 0 || b.Hi&other.Hi != 0
+func (b *Mask) ContainsAny(other *Mask) bool {
+	return b.bits[0]&other.bits[0] != 0 ||
+		b.bits[1]&other.bits[1] != 0 ||
+		b.bits[2]&other.bits[2] != 0 ||
+		b.bits[3]&other.bits[3] != 0
 }
 
 // TotalBitsSet returns how many bits are set in this mask.
 func (b *Mask) TotalBitsSet() int {
-	return bits.OnesCount64(b.Hi) + bits.OnesCount64(b.Lo)
+	return bits.OnesCount64(b.bits[0]) + bits.OnesCount64(b.bits[1]) + bits.OnesCount64(b.bits[2]) + bits.OnesCount64(b.bits[3])
 }
diff --git a/ecs/cache.go b/ecs/cache.go
index 1ecce56..abe8171 100644
--- a/ecs/cache.go
+++ b/ecs/cache.go
@@ -98,7 +98,7 @@ func (c *Cache) addArchetype(arch *archetype) {
 	if !arch.HasRelation() {
 		for i := range c.filters {
 			e := &c.filters[i]
-			if !e.Filter.Matches(arch.Mask) {
+			if !e.Filter.Matches(&arch.Mask) {
 				continue
 			}
 			e.Archetypes.Add(arch)
@@ -108,7 +108,7 @@ func (c *Cache) addArchetype(arch *archetype) {
 
 	for i := range c.filters {
 		e := &c.filters[i]
-		if !e.Filter.Matches(arch.Mask) {
+		if !e.Filter.Matches(&arch.Mask) {
 			continue
 		}
 		if rf, ok := e.Filter.(*RelationFilter); ok {
@@ -135,7 +135,7 @@ func (c *Cache) removeArchetype(arch *archetype) {
 	for i := range c.filters {
 		e := &c.filters[i]
 
-		if e.Indices == nil && e.Filter.Matches(arch.Mask) {
+		if e.Indices == nil && e.Filter.Matches(&arch.Mask) {
 			c.mapArchetypes(e)
 		}
 
diff --git a/ecs/filter.go b/ecs/filter.go
index f528e12..4253bb1 100644
--- a/ecs/filter.go
+++ b/ecs/filter.go
@@ -8,7 +8,7 @@ package ecs
 // For advanced filtering, see package [github.com/mlange-42/arche/filter].
 type Filter interface {
 	// Matches the filter against a mask, i.e. a component composition.
-	Matches(bits Mask) bool
+	Matches(bits *Mask) bool
 }
 
 // MaskFilter is a [Filter] for including and excluding certain components.
@@ -20,8 +20,8 @@ type MaskFilter struct {
 }
 
 // Matches the filter against a mask.
-func (f *MaskFilter) Matches(bits Mask) bool {
-	return bits.Contains(f.Include) && (f.Exclude.IsZero() || !bits.ContainsAny(f.Exclude))
+func (f *MaskFilter) Matches(bits *Mask) bool {
+	return bits.Contains(&f.Include) && (!bits.ContainsAny(&f.Exclude) || f.Exclude.IsZero())
 }
 
 // RelationFilter is a [Filter] for a [Relation] target, in addition to components.
@@ -42,7 +42,7 @@ func NewRelationFilter(filter Filter, target Entity) RelationFilter {
 }
 
 // Matches the filter against a mask.
-func (f *RelationFilter) Matches(bits Mask) bool {
+func (f *RelationFilter) Matches(bits *Mask) bool {
 	return f.Filter.Matches(bits)
 }
 
@@ -56,6 +56,6 @@ type CachedFilter struct {
 }
 
 // Matches the filter against a mask.
-func (f *CachedFilter) Matches(bits Mask) bool {
+func (f *CachedFilter) Matches(bits *Mask) bool {
 	return f.filter.Matches(bits)
 }
diff --git a/ecs/id_map.go b/ecs/id_map.go
index 035bf5d..bdc8940 100644
--- a/ecs/id_map.go
+++ b/ecs/id_map.go
@@ -1,8 +1,8 @@
 package ecs
 
 const (
-	numChunks = 8
 	chunkSize = 16
+	numChunks = MaskTotalBits / chunkSize
 )
 
 // idMap maps component IDs to values.
diff --git a/ecs/pool.go b/ecs/pool.go
index 3fc3ccf..802e436 100644
--- a/ecs/pool.go
+++ b/ecs/pool.go
@@ -98,7 +98,7 @@ func (p *entityPool) Available() int {
 type bitPool struct {
 	bits      [MaskTotalBits]uint8
 	next      uint8
-	length    uint8
+	length    uint16
 	available uint8
 }
 
@@ -116,9 +116,9 @@ func (p *bitPool) Get() uint8 {
 // Allocates and returns a new bit. For internal use.
 func (p *bitPool) getNew() uint8 {
 	if p.length >= MaskTotalBits {
-		panic("run out of the maximum of 128 bits")
+		panic("run out of the maximum of 256 bits")
 	}
-	b := p.length
+	b := uint8(p.length)
 	p.bits[p.length] = b
 	p.length++
 	return b
diff --git a/ecs/util.go b/ecs/util.go
index 75f65be..e499020 100644
--- a/ecs/util.go
+++ b/ecs/util.go
@@ -30,21 +30,17 @@ func maskToTypes(mask Mask, reg *componentRegistry[ID]) []componentType {
 	count := int(mask.TotalBitsSet())
 	types := make([]componentType, count)
 
-	start := 0
-	end := MaskTotalBits
-	if mask.Lo == 0 {
-		start = wordSize
-	}
-	if mask.Hi == 0 {
-		end = wordSize
-	}
-
 	idx := 0
-	for i := start; i < end; i++ {
-		id := ID(i)
-		if mask.Get(id) {
-			types[idx] = componentType{ID: id, Type: reg.Types[id]}
-			idx++
+	for i := range mask.bits {
+		if mask.bits[i] == 0 {
+			continue
+		}
+		for j := 0; j < wordSize; j++ {
+			id := ID(i*wordSize + j)
+			if mask.Get(id) {
+				types[idx] = componentType{ID: id, Type: reg.Types[id]}
+				idx++
+			}
 		}
 	}
 	return types
diff --git a/filter/filter.go b/filter/filter.go
index e5d551a..8217627 100644
--- a/filter/filter.go
+++ b/filter/filter.go
@@ -22,8 +22,9 @@ func Any(comps ...ecs.ID) ANY {
 }
 
 // Matches the filter against a mask.
-func (f ANY) Matches(bits ecs.Mask) bool {
-	return bits.ContainsAny(ecs.Mask(f))
+func (f ANY) Matches(bits *ecs.Mask) bool {
+	m := ecs.Mask(f)
+	return bits.ContainsAny(&m)
 }
 
 // NoneOF matches entities that are missing all the given components.
@@ -35,8 +36,9 @@ func NoneOf(comps ...ecs.ID) NoneOF {
 }
 
 // Matches the filter against a mask.
-func (f NoneOF) Matches(bits ecs.Mask) bool {
-	return !bits.ContainsAny(ecs.Mask(f))
+func (f NoneOF) Matches(bits *ecs.Mask) bool {
+	m := ecs.Mask(f)
+	return !bits.ContainsAny(&m)
 }
 
 // AnyNOT matches entities that are missing any of the given components.
@@ -48,8 +50,9 @@ func AnyNot(comps ...ecs.ID) AnyNOT {
 }
 
 // Matches the filter against a mask.
-func (f AnyNOT) Matches(bits ecs.Mask) bool {
-	return !bits.Contains(ecs.Mask(f))
+func (f AnyNOT) Matches(bits *ecs.Mask) bool {
+	m := ecs.Mask(f)
+	return !bits.Contains(&m)
 }
 
 // AND combines two filters using AND.
@@ -68,7 +71,7 @@ func And(l, r ecs.Filter) *AND {
 }
 
 // Matches the filter against a mask.
-func (f *AND) Matches(bits ecs.Mask) bool {
+func (f *AND) Matches(bits *ecs.Mask) bool {
 	return f.L.Matches(bits) && f.R.Matches(bits)
 }
 
@@ -88,7 +91,7 @@ func Or(l, r ecs.Filter) *OR {
 }
 
 // Matches the filter against a mask.
-func (f *OR) Matches(bits ecs.Mask) bool {
+func (f *OR) Matches(bits *ecs.Mask) bool {
 	return f.L.Matches(bits) || f.R.Matches(bits)
 }
 
@@ -108,7 +111,7 @@ func XOr(l, r ecs.Filter) *XOR {
 }
 
 // Matches the filter against a mask.
-func (f *XOR) Matches(bits ecs.Mask) bool {
+func (f *XOR) Matches(bits *ecs.Mask) bool {
 	return f.L.Matches(bits) != f.R.Matches(bits)
 }
 
@@ -125,6 +128,6 @@ func Not(f ecs.Filter) *NOT {
 }
 
 // Matches the filter against a mask.
-func (f *NOT) Matches(bits ecs.Mask) bool {
+func (f *NOT) Matches(bits *ecs.Mask) bool {
 	return !f.F.Matches(bits)
 }


Test patch:
diff --git a/benchmark/competition/array_of_structs/arche_test.go b/benchmark/competition/array_of_structs/arche_test.go
index 300d6fb..e4307d7 100644
--- a/benchmark/competition/array_of_structs/arche_test.go
+++ b/benchmark/competition/array_of_structs/arche_test.go
@@ -6,22 +6,27 @@ import (
 	"github.com/mlange-42/arche/ecs"
 )
 
-type Struct16B0 struct{ ecs.Mask }
-type Struct16B1 struct{ ecs.Mask }
-type Struct16B2 struct{ ecs.Mask }
-type Struct16B3 struct{ ecs.Mask }
-type Struct16B4 struct{ ecs.Mask }
-type Struct16B5 struct{ ecs.Mask }
-type Struct16B6 struct{ ecs.Mask }
-type Struct16B7 struct{ ecs.Mask }
-type Struct16B8 struct{ ecs.Mask }
-type Struct16B9 struct{ ecs.Mask }
-type Struct16B10 struct{ ecs.Mask }
-type Struct16B11 struct{ ecs.Mask }
-type Struct16B12 struct{ ecs.Mask }
-type Struct16B13 struct{ ecs.Mask }
-type Struct16B14 struct{ ecs.Mask }
-type Struct16B15 struct{ ecs.Mask }
+type Mask struct {
+	Lo uint64
+	Hi uint64
+}
+
+type Struct16B0 struct{ Mask }
+type Struct16B1 struct{ Mask }
+type Struct16B2 struct{ Mask }
+type Struct16B3 struct{ Mask }
+type Struct16B4 struct{ Mask }
+type Struct16B5 struct{ Mask }
+type Struct16B6 struct{ Mask }
+type Struct16B7 struct{ Mask }
+type Struct16B8 struct{ Mask }
+type Struct16B9 struct{ Mask }
+type Struct16B10 struct{ Mask }
+type Struct16B11 struct{ Mask }
+type Struct16B12 struct{ Mask }
+type Struct16B13 struct{ Mask }
+type Struct16B14 struct{ Mask }
+type Struct16B15 struct{ Mask }
 
 func runArche16B(b *testing.B, count int) {
 	b.StopTimer()
diff --git a/ecs/bitmask_test.go b/ecs/bitmask_test.go
index d79e8ba..3b78ef3 100644
--- a/ecs/bitmask_test.go
+++ b/ecs/bitmask_test.go
@@ -8,18 +8,26 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func all(ids ...ecs.ID) *ecs.Mask {
+	mask := ecs.All(ids...)
+	return &mask
+}
+
 func TestBitMask(t *testing.T) {
-	mask := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))
+	mask := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27), ecs.ID(200))
 
-	assert.Equal(t, 4, mask.TotalBitsSet())
+	assert.Equal(t, 5, mask.TotalBitsSet())
 
 	assert.True(t, mask.Get(1))
 	assert.True(t, mask.Get(2))
 	assert.True(t, mask.Get(13))
 	assert.True(t, mask.Get(27))
+	assert.True(t, mask.Get(200))
 
 	assert.False(t, mask.Get(0))
 	assert.False(t, mask.Get(3))
+	assert.False(t, mask.Get(199))
+	assert.False(t, mask.Get(201))
 
 	mask.Set(ecs.ID(0), true)
 	mask.Set(ecs.ID(1), false)
@@ -30,8 +38,8 @@ func TestBitMask(t *testing.T) {
 	other1 := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(32))
 	other2 := ecs.All(ecs.ID(0), ecs.ID(2))
 
-	assert.False(t, mask.Contains(other1))
-	assert.True(t, mask.Contains(other2))
+	assert.False(t, mask.Contains(&other1))
+	assert.True(t, mask.Contains(&other2))
 
 	mask.Reset()
 	assert.Equal(t, 0, mask.TotalBitsSet())
@@ -40,33 +48,34 @@ func TestBitMask(t *testing.T) {
 	other1 = ecs.All(ecs.ID(1), ecs.ID(32))
 	other2 = ecs.All(ecs.ID(0), ecs.ID(32))
 
-	assert.True(t, mask.ContainsAny(other1))
-	assert.False(t, mask.ContainsAny(other2))
+	assert.True(t, mask.ContainsAny(&other1))
+	assert.False(t, mask.ContainsAny(&other2))
 }
 
 func TestBitMaskWithoutExclusive(t *testing.T) {
 	mask := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))
-	assert.True(t, mask.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
-	assert.True(t, mask.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
 
-	assert.False(t, mask.Matches(ecs.All(ecs.ID(1), ecs.ID(2))))
+	assert.True(t, mask.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
+	assert.True(t, mask.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
+
+	assert.False(t, mask.Matches(all(ecs.ID(1), ecs.ID(2))))
 
 	without := mask.Without(ecs.ID(3))
 
-	assert.True(t, without.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
-	assert.True(t, without.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
+	assert.True(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
+	assert.True(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
 
-	assert.False(t, without.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
-	assert.False(t, without.Matches(ecs.All(ecs.ID(1), ecs.ID(2))))
+	assert.False(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
+	assert.False(t, without.Matches(all(ecs.ID(1), ecs.ID(2))))
 
 	excl := mask.Exclusive()
 
-	assert.True(t, excl.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
-	assert.False(t, excl.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
-	assert.False(t, excl.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
+	assert.True(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
+	assert.False(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
+	assert.False(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
 }
 
-func TestBitMask128(t *testing.T) {
+func TestBitMask256(t *testing.T) {
 	for i := 0; i < ecs.MaskTotalBits; i++ {
 		mask := ecs.All(ecs.ID(i))
 		assert.Equal(t, 1, mask.TotalBitsSet())
@@ -83,17 +92,17 @@ func TestBitMask128(t *testing.T) {
 
 	mask = ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27), ecs.ID(63), ecs.ID(64), ecs.ID(65))
 
-	assert.True(t, mask.Contains(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(64))))
-	assert.False(t, mask.Contains(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(90))))
+	assert.True(t, mask.Contains(all(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(64))))
+	assert.False(t, mask.Contains(all(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(90))))
 
-	assert.True(t, mask.ContainsAny(ecs.All(ecs.ID(6), ecs.ID(65), ecs.ID(111))))
-	assert.False(t, mask.ContainsAny(ecs.All(ecs.ID(6), ecs.ID(66), ecs.ID(90))))
+	assert.True(t, mask.ContainsAny(all(ecs.ID(6), ecs.ID(65), ecs.ID(111))))
+	assert.False(t, mask.ContainsAny(all(ecs.ID(6), ecs.ID(66), ecs.ID(90))))
 }
 
 func TestBitMask64(t *testing.T) {
 	mask := newBitMask64(ecs.ID(1))
 	assert.True(t, mask.Get(ecs.ID(1)))
-	for i := 0; i < ecs.MaskTotalBits/2; i++ {
+	for i := 0; i < 64; i++ {
 		mask.Set(ecs.ID(i), true)
 		assert.True(t, mask.Get(ecs.ID(i)))
 		mask.Set(ecs.ID(i), false)
@@ -121,7 +130,7 @@ func BenchmarkBitmask64Get(b *testing.B) {
 	_ = v
 }
 
-func BenchmarkBitmask128Get(b *testing.B) {
+func BenchmarkBitmask256Get(b *testing.B) {
 	b.StopTimer()
 	mask := ecs.All()
 	for i := 0; i < ecs.MaskTotalBits; i++ {
@@ -155,7 +164,7 @@ func BenchmarkBitmaskContains(b *testing.B) {
 
 	var v bool
 	for i := 0; i < b.N; i++ {
-		v = mask.Contains(filter)
+		v = mask.Contains(&filter)
 	}
 
 	b.StopTimer()
@@ -176,7 +185,7 @@ func BenchmarkBitmaskContainsAny(b *testing.B) {
 
 	var v bool
 	for i := 0; i < b.N; i++ {
-		v = mask.ContainsAny(filter)
+		v = mask.ContainsAny(&filter)
 	}
 
 	b.StopTimer()
@@ -186,12 +195,12 @@ func BenchmarkBitmaskContainsAny(b *testing.B) {
 
 func BenchmarkMaskFilter(b *testing.B) {
 	b.StopTimer()
-	mask := ecs.All(0, 1, 2).Without()
+	mask := ecs.All(0, 1, 2).Without(3)
 	bits := ecs.All(0, 1, 2)
 	b.StartTimer()
 	var v bool
 	for i := 0; i < b.N; i++ {
-		v = mask.Matches(bits)
+		v = mask.Matches(&bits)
 	}
 	b.StopTimer()
 	v = !v
@@ -200,7 +209,7 @@ func BenchmarkMaskFilter(b *testing.B) {
 
 func BenchmarkMaskFilterNoPointer(b *testing.B) {
 	b.StopTimer()
-	mask := maskFilterPointer{ecs.All(0, 1, 2), ecs.All()}
+	mask := maskFilterPointer{ecs.All(0, 1, 2), ecs.All(3)}
 	bits := ecs.All(0, 1, 2)
 	b.StartTimer()
 	var v bool
@@ -233,14 +242,14 @@ func BenchmarkMask(b *testing.B) {
 	b.StartTimer()
 	var v bool
 	for i := 0; i < b.N; i++ {
-		v = mask.Matches(bits)
+		v = mask.Matches(&bits)
 	}
 	b.StopTimer()
 	v = !v
 	_ = v
 }
 
-// bitMask64 is there just for performance comparison with the new 128 bit Mask.
+// bitMask64 is there just for performance comparison with the new 256 bit Mask.
 type bitMask64 uint64
 
 func newBitMask64(ids ...ecs.ID) bitMask64 {
@@ -268,17 +277,18 @@ type maskFilterPointer struct {
 	Exclude ecs.Mask
 }
 
-// Matches matches a filter against a mask.
+// Matches a filter against a mask.
 func (f maskFilterPointer) Matches(bits ecs.Mask) bool {
-	return bits.Contains(f.Mask) &&
-		(f.Exclude.IsZero() || !bits.ContainsAny(f.Exclude))
+	return bits.Contains(&f.Mask) &&
+		(f.Exclude.IsZero() || !bits.ContainsAny(&f.Exclude))
 }
 
 type maskPointer ecs.Mask
 
-// Matches matches a filter against a mask.
+// Matches a filter against a mask.
 func (f *maskPointer) Matches(bits ecs.Mask) bool {
-	return bits.Contains(ecs.Mask(*f))
+	m := ecs.Mask(*f)
+	return bits.Contains(&m)
 }
 
 func ExampleMask() {
diff --git a/ecs/event_test.go b/ecs/event_test.go
index 677771d..b65eabc 100644
--- a/ecs/event_test.go
+++ b/ecs/event_test.go
@@ -80,5 +80,5 @@ func ExampleEntityEvent() {
 	world.SetListener(listener)
 
 	world.NewEntity()
-	// Output: &{{1 0} {0 0} {0 0} [] [] [] 1 {0 0} {0 0} false}
+	// Output: &{{1 0} {[0 0 0 0]} {[0 0 0 0]} [] [] [] 1 {0 0} {0 0} false}
 }
diff --git a/ecs/filter_test.go b/ecs/filter_test.go
index a5050d0..930d13a 100644
--- a/ecs/filter_test.go
+++ b/ecs/filter_test.go
@@ -10,11 +10,11 @@ import (
 func TestCachedMaskFilter(t *testing.T) {
 	f := ecs.All(1, 2, 3).Without(4)
 
-	assert.True(t, f.Matches(ecs.All(1, 2, 3)))
-	assert.True(t, f.Matches(ecs.All(1, 2, 3, 5)))
+	assert.True(t, f.Matches(all(1, 2, 3)))
+	assert.True(t, f.Matches(all(1, 2, 3, 5)))
 
-	assert.False(t, f.Matches(ecs.All(1, 2)))
-	assert.False(t, f.Matches(ecs.All(1, 2, 3, 4)))
+	assert.False(t, f.Matches(all(1, 2)))
+	assert.False(t, f.Matches(all(1, 2, 3, 4)))
 }
 
 func TestCachedFilter(t *testing.T) {
@@ -23,8 +23,8 @@ func TestCachedFilter(t *testing.T) {
 	f := ecs.All(1, 2, 3)
 	fc := w.Cache().Register(f)
 
-	assert.Equal(t, f.Matches(ecs.All(1, 2, 3)), fc.Matches(ecs.All(1, 2, 3)))
-	assert.Equal(t, f.Matches(ecs.All(1, 2)), fc.Matches(ecs.All(1, 2)))
+	assert.Equal(t, f.Matches(all(1, 2, 3)), fc.Matches(all(1, 2, 3)))
+	assert.Equal(t, f.Matches(all(1, 2)), fc.Matches(all(1, 2)))
 
 	w.Cache().Unregister(&fc)
 }
diff --git a/ecs/id_map_test.go b/ecs/id_map_test.go
index 0987775..0c154f2 100644
--- a/ecs/id_map_test.go
+++ b/ecs/id_map_test.go
@@ -12,10 +12,12 @@ func TestIDMap(t *testing.T) {
 	e0 := Entity{0, 0}
 	e1 := Entity{1, 0}
 	e121 := Entity{121, 0}
+	e200 := Entity{200, 0}
 
 	m.Set(0, &e0)
 	m.Set(1, &e1)
 	m.Set(121, &e121)
+	m.Set(200, &e200)
 
 	e, ok := m.Get(0)
 	assert.True(t, ok)
@@ -29,6 +31,10 @@ func TestIDMap(t *testing.T) {
 	assert.True(t, ok)
 	assert.Equal(t, e121, *e)
 
+	e, ok = m.Get(200)
+	assert.True(t, ok)
+	assert.Equal(t, e200, *e)
+
 	e, ok = m.Get(15)
 	assert.False(t, ok)
 	assert.Nil(t, e)
@@ -49,10 +55,12 @@ func TestIDMapPointers(t *testing.T) {
 	e0 := Entity{0, 0}
 	e1 := Entity{1, 0}
 	e121 := Entity{121, 0}
+	e200 := Entity{200, 0}
 
 	m.Set(0, e0)
 	m.Set(1, e1)
 	m.Set(121, e121)
+	m.Set(200, e200)
 
 	e, ok := m.GetPointer(0)
 	assert.True(t, ok)
@@ -66,6 +74,10 @@ func TestIDMapPointers(t *testing.T) {
 	assert.True(t, ok)
 	assert.Equal(t, e121, *e)
 
+	e, ok = m.GetPointer(200)
+	assert.True(t, ok)
+	assert.Equal(t, e200, *e)
+
 	e, ok = m.GetPointer(15)
 	assert.False(t, ok)
 	assert.Nil(t, e)
diff --git a/ecs/query_test.go b/ecs/query_test.go
index 707516e..7191650 100644
--- a/ecs/query_test.go
+++ b/ecs/query_test.go
@@ -10,10 +10,10 @@ func TestMask(t *testing.T) {
 	filter := All(0, 2, 4)
 	other := All(0, 1, 2)
 
-	assert.False(t, filter.Matches(other))
+	assert.False(t, filter.Matches(&other))
 
 	other = All(0, 1, 2, 3, 4)
-	assert.True(t, filter.Matches(other))
+	assert.True(t, filter.Matches(&other))
 }
 
 func TestQuery(t *testing.T) {
@@ -260,7 +260,7 @@ func TestQueryCount(t *testing.T) {
 
 type testFilter struct{}
 
-func (f testFilter) Matches(bits Mask) bool {
+func (f testFilter) Matches(bits *Mask) bool {
 	return true
 }
 
@@ -282,7 +282,8 @@ func TestQueryInterface(t *testing.T) {
 	w.Add(e3, posID, rotID)
 	w.Add(e4, rotID)
 
-	q := w.Query(testFilter{})
+	filter := testFilter{}
+	q := w.Query(&filter)
 
 	cnt := 0
 	for q.Next() {
diff --git a/ecs/world_examples_test.go b/ecs/world_examples_test.go
index e0b5c89..18d9172 100644
--- a/ecs/world_examples_test.go
+++ b/ecs/world_examples_test.go
@@ -281,7 +281,7 @@ func ExampleWorld_SetListener() {
 	world.SetListener(listener)
 
 	world.NewEntity()
-	// Output: &{{1 0} {0 0} {0 0} [] [] [] 1 {0 0} {0 0} false}
+	// Output: &{{1 0} {[0 0 0 0]} {[0 0 0 0]} [] [] [] 1 {0 0} {0 0} false}
 }
 
 func ExampleWorld_Stats() {
diff --git a/ecs/world_test.go b/ecs/world_test.go
index 8ac14eb..0be9789 100644
--- a/ecs/world_test.go
+++ b/ecs/world_test.go
@@ -1586,7 +1586,7 @@ func Test1000Archetypes(t *testing.T) {
 	ids[9] = ComponentID[testStruct9](&w)
 
 	for i := 0; i < 1024; i++ {
-		mask := Mask{uint64(i), 0}
+		mask := Mask{[4]uint64{uint64(i), 0, 0, 0}}
 		add := make([]ID, 0, 10)
 		for j := 0; j < 10; j++ {
 			id := ID(j)
diff --git a/filter/filter_test.go b/filter/filter_test.go
index f1ea3bb..8795e01 100644
--- a/filter/filter_test.go
+++ b/filter/filter_test.go
@@ -183,7 +183,7 @@ func TestFilter(t *testing.T) {
 }
 
 func match(f ecs.Filter, m ecs.Mask) bool {
-	return f.Matches(m)
+	return f.Matches(&m)
 }
 
 func TestInterface(t *testing.T) {
@@ -204,7 +204,7 @@ func BenchmarkFilterStackOr(b *testing.B) {
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		_ = filter.Matches(ecs.Mask(mask))
+		_ = filter.Matches(&mask)
 	}
 }
 
@@ -216,7 +216,7 @@ func BenchmarkFilterHeapOr(b *testing.B) {
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		_ = filter.Matches(ecs.Mask(mask))
+		_ = filter.Matches(&mask)
 	}
 }
 
@@ -231,7 +231,7 @@ func BenchmarkFilterStack5And(b *testing.B) {
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		_ = filter.Matches(ecs.Mask(mask))
+		_ = filter.Matches(&mask)
 	}
 }
 
@@ -243,6 +243,6 @@ func BenchmarkFilterHeap5And(b *testing.B) {
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		_ = filter.Matches(ecs.Mask(mask))
+		_ = filter.Matches(&mask)
 	}
 }