Repository: mlange-42/arche (go)

Issue:
Benchmark and profile archetype transitions
Until now, most of the optimization went into pure iteration and random access speed.

The weak point of an archetype-base ECS, component changes and resulting archetype transfer, are rarely benchmarked or profiled.

Pull request:
Optimize Add/Remove/Exchange
Fixes #90

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3de2acf..6b9452d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@
 * Generic queries support optional, additional and excluded components (#53, #56, #58, #59, #60, #63)
 * Logic filters for complex queries (#54, #58, #61)
 * `Query` and `World` have a method `Mask(Entity)` to access archetype masks (#65)
-* Generic query `Get` method returns the entity as first element (#83)
+* Generic query `Get` method returns all queried components (#83)
 * Added method `World.Stats()` for inspecting otherwise inaccessible world statistics (#67)
 * Entities can be initialized with components, via ID as well as using generics (#76)
 * A listener function can be registered to the world, for notification on entity changes (#77)
@@ -21,6 +21,7 @@
 * Boilerplate code for generic filters and queries is auto-generated with `go generate` (#64)
 * Ensure 100% test coverage by adding a CI check for it (#68)
 * `World.RemEntity(Entity)` is now `World.RemoveEntity(Entity)` (#87)
+* Optimization of adding/removing components, with 2-3x speedup and vast reduction of (number of) allocations (#93)
 
 ## [[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 aba7439..1fe7044 100644
--- a/README.md
+++ b/README.md
@@ -89,7 +89,7 @@ func main() {
 		// Iterate it
 		for query.Next() {
 			// Component access through the Query.
-			_, pos, vel := query.Get()
+			pos, vel := query.Get()
 			// Update component fields.
 			pos.X += vel.X
 			pos.Y += vel.Y
diff --git a/benchmark/profile/exchange/main.go b/benchmark/profile/exchange/main.go
index d53b9c9..5b4efe1 100644
--- a/benchmark/profile/exchange/main.go
+++ b/benchmark/profile/exchange/main.go
@@ -3,8 +3,8 @@ package main
 // Profiling:
 // go build ./benchmark/profile/exchange
 // exchange
-// go tool pprof -http=":8000" -nodefraction=0.001 main cpu.pprof
-// go tool pprof -http=":8000" -nodefraction=0.001 main mem.pprof
+// go tool pprof -http=":8000" -nodefraction=0.001 exchange cpu.pprof
+// go tool pprof -http=":8000" -nodefraction=0.001 exchange mem.pprof
 
 import (
 	"github.com/mlange-42/arche/ecs"
@@ -28,7 +28,7 @@ type rotation struct {
 func main() {
 
 	count := 100
-	iters := 250
+	iters := 1000
 	entities := 1000
 
 	stop := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
diff --git a/benchmark/profile/iter/main.go b/benchmark/profile/iter/main.go
index 1f03915..d6effcc 100644
--- a/benchmark/profile/iter/main.go
+++ b/benchmark/profile/iter/main.go
@@ -3,8 +3,8 @@ package main
 // Profiling:
 // go build ./benchmark/profile/iter
 // iter
-// go tool pprof -http=":8000" -nodefraction=0.001 main cpu.pprof
-// go tool pprof -http=":8000" -nodefraction=0.001 main mem.pprof
+// go tool pprof -http=":8000" -nodefraction=0.001 iter cpu.pprof
+// go tool pprof -http=":8000" -nodefraction=0.001 iter mem.pprof
 
 import (
 	"github.com/mlange-42/arche/ecs"
diff --git a/ecs/archetype.go b/ecs/archetype.go
index 8636e80..683efd5 100644
--- a/ecs/archetype.go
+++ b/ecs/archetype.go
@@ -1,7 +1,6 @@
 package ecs
 
 import (
-	"reflect"
 	"unsafe"
 )
 
@@ -11,17 +10,15 @@ type archetype struct {
 	Ids  []ID
 	// Indirection to avoid a fixed-size array of storages
 	// Increases access time by 50-100%
-	indices    [MaskTotalBits]uint8
-	entities   storage
+	references [MaskTotalBits]*storage
+	entities   genericStorage[Entity]
 	components []storage
-	toAdd      map[ID]*archetype
-	toRemove   map[ID]*archetype
+	toAdd      []*archetype
+	toRemove   []*archetype
 }
 
-var entityType = reflect.TypeOf(Entity{})
-
 // Init initializes an archetype
-func (a *archetype) Init(capacityIncrement int, components ...componentType) {
+func (a *archetype) Init(capacityIncrement int, forStorage bool, components ...componentType) {
 	var mask BitMask
 	if len(components) > 0 {
 		a.Ids = make([]ID, len(components))
@@ -37,22 +34,22 @@ func (a *archetype) Init(capacityIncrement int, components ...componentType) {
 
 		mask.Set(c.ID, true)
 		a.Ids[i] = c.ID
-		a.indices[c.ID] = uint8(i)
 		comps[i] = storage{}
-		comps[i].Init(c.Type, capacityIncrement)
+		comps[i].Init(c.Type, capacityIncrement, forStorage)
+		a.references[c.ID] = &comps[i]
 	}
 
 	a.Mask = mask
 	a.components = comps
-	a.entities = storage{}
-	a.toAdd = map[ID]*archetype{}
-	a.toRemove = map[ID]*archetype{}
-	a.entities.Init(entityType, capacityIncrement)
+	a.entities = genericStorage[Entity]{}
+	a.toAdd = make([]*archetype, MaskTotalBits)
+	a.toRemove = make([]*archetype, MaskTotalBits)
+	a.entities.Init(capacityIncrement, forStorage)
 }
 
 // GetEntity returns the entity at the given index
 func (a *archetype) GetEntity(index uint32) Entity {
-	return *(*Entity)(a.entities.Get(index))
+	return a.entities.Get(index)
 }
 
 // Get returns the component with the given ID at the given index
@@ -60,7 +57,7 @@ func (a *archetype) Get(index uint32, id ID) unsafe.Pointer {
 	if !a.Mask.Get(id) {
 		return nil
 	}
-	return a.components[a.indices[id]].Get(index)
+	return a.references[id].Get(index)
 }
 
 // GetUnsafe returns the component with the given ID at the given index,
@@ -68,15 +65,20 @@ func (a *archetype) Get(index uint32, id ID) unsafe.Pointer {
 //
 // This is used by queries, where the entity is guaranteed to be in the archetype.
 func (a *archetype) GetUnsafe(index uint32, id ID) unsafe.Pointer {
-	return a.components[a.indices[id]].Get(index)
+	return a.references[id].Get(index)
 }
 
 // Add adds an entity with zeroed components to the archetype
-func (a *archetype) Alloc(entity Entity) uint32 {
-	idx := a.entities.Add(&entity)
+func (a *archetype) Alloc(entity Entity, zero bool) uint32 {
+	idx := a.entities.Add(entity)
 	len := len(a.components)
+
 	for i := 0; i < len; i++ {
-		a.components[i].Alloc()
+		comp := &a.components[i]
+		idx := comp.Alloc()
+		if zero {
+			comp.Zero(idx)
+		}
 	}
 	return idx
 }
@@ -86,25 +88,9 @@ func (a *archetype) Add(entity Entity, components ...Component) uint32 {
 	if len(components) != len(a.Ids) {
 		panic("Invalid number of components")
 	}
-	idx := a.entities.Add(&entity)
-	for _, c := range components {
-		a.components[a.indices[c.ID]].Add(c.Component)
-	}
-	return idx
-}
-
-// AddPointer adds an entity with components to the archetype, using pointers
-func (a *archetype) AddPointer(entity Entity, components ...componentPointer) uint32 {
-	if len(components) != len(a.Ids) {
-		panic("Invalid number of components")
-	}
-	idx := a.entities.Add(&entity)
+	idx := a.entities.Add(entity)
 	for _, c := range components {
-		if c.Pointer == nil {
-			a.components[a.indices[c.ID]].Alloc()
-		} else {
-			a.components[a.indices[c.ID]].AddPointer(c.Pointer)
-		}
+		a.references[c.ID].Add(c.Component)
 	}
 	return idx
 }
@@ -141,19 +127,29 @@ func (a *archetype) Cap() uint32 {
 
 // Set overwrites a component with the data behind the given pointer
 func (a *archetype) Set(index uint32, id ID, comp interface{}) unsafe.Pointer {
-	return a.components[a.indices[id]].Set(index, comp)
+	return a.references[id].Set(index, comp)
+}
+
+// SetPointer overwrites a component with the data behind the given pointer
+func (a *archetype) SetPointer(index uint32, id ID, comp unsafe.Pointer) unsafe.Pointer {
+	return a.references[id].SetPointer(index, comp)
+}
+
+// Zero resets th memory at the given position
+func (a *archetype) Zero(index uint32, id ID) {
+	a.references[id].Zero(index)
 }
 
 // GetTransitionAdd returns the archetype resulting from adding a component
 func (a *archetype) GetTransitionAdd(id ID) (*archetype, bool) {
-	p, ok := a.toAdd[id]
-	return p, ok
+	p := a.toAdd[id]
+	return p, p != nil
 }
 
 // GetTransitionRemove returns the archetype resulting from removing a component
 func (a *archetype) GetTransitionRemove(id ID) (*archetype, bool) {
-	p, ok := a.toRemove[id]
-	return p, ok
+	p := a.toRemove[id]
+	return p, p != nil
 }
 
 // SetTransitionAdd sets the archetype resulting from adding a component
diff --git a/ecs/entity_pool.go b/ecs/entity_pool.go
index 1b76cc3..7b1b426 100644
--- a/ecs/entity_pool.go
+++ b/ecs/entity_pool.go
@@ -6,8 +6,10 @@ import (
 
 // newEntityPool creates a new, initialized Entity pool
 func newEntityPool(capacityIncrement int) entityPool {
+	entities := make([]Entity, 1, capacityIncrement)
+	entities[0] = Entity{0, math.MaxUint16}
 	return entityPool{
-		entities:          []Entity{{0, math.MaxUint16}},
+		entities:          entities,
 		next:              0,
 		available:         0,
 		capacityIncrement: capacityIncrement,
diff --git a/ecs/storage.go b/ecs/storage.go
index 5ff9c7a..90b8491 100644
--- a/ecs/storage.go
+++ b/ecs/storage.go
@@ -18,24 +18,28 @@ type storage struct {
 }
 
 // Init initializes a storage
-func (s *storage) Init(tp reflect.Type, increment int) {
+func (s *storage) Init(tp reflect.Type, increment int, forStorage bool) {
 	size := tp.Size()
 	align := uintptr(tp.Align())
 	size = (size + (align - 1)) / align * align
 
-	s.buffer = reflect.New(reflect.ArrayOf(1, tp)).Elem()
+	cap := 1
+	if forStorage {
+		cap = increment
+	}
+	s.buffer = reflect.New(reflect.ArrayOf(cap, tp)).Elem()
 	s.bufferAddress = s.buffer.Addr().UnsafePointer()
 	s.typeOf = tp
 	s.itemSize = size
 	s.len = 0
-	s.cap = 1
+	s.cap = uint32(cap)
 	s.capacityIncrement = uint32(increment)
 }
 
 // Get retrieves an unsafe pointer to an element
 func (s *storage) Get(index uint32) unsafe.Pointer {
 	ptr := unsafe.Add(s.bufferAddress, uintptr(index)*s.itemSize)
-	return unsafe.Pointer(ptr)
+	return ptr
 }
 
 // Add adds an element to the end of the storage
@@ -50,26 +54,29 @@ func (s *storage) Add(value interface{}) (index uint32) {
 func (s *storage) AddPointer(value unsafe.Pointer) (index uint32) {
 	s.extend()
 	s.len++
-	s.setPointer(s.len-1, value)
+	s.SetPointer(s.len-1, value)
 	return s.len - 1
 }
 
-// Alloc adds an empty element to the end of the storage
+// Alloc adds an empty element to the end of the storage.
+// It does not zero the storage!
 func (s *storage) Alloc() (index uint32) {
 	s.extend()
 	s.len++
-	s.Zero(s.len - 1)
+	//s.Zero(s.len - 1)
 	return s.len - 1
 }
 
 func (s *storage) extend() {
-	if s.itemSize > 0 && s.cap < s.len+1 {
-		old := s.buffer
-		s.cap = s.capacityIncrement * ((s.cap + s.capacityIncrement) / s.capacityIncrement)
-		s.buffer = reflect.New(reflect.ArrayOf(int(s.cap), s.typeOf)).Elem()
-		s.bufferAddress = s.buffer.Addr().UnsafePointer()
-		reflect.Copy(s.buffer, old)
+	if s.cap > s.len || s.itemSize == 0 {
+		return
 	}
+
+	old := s.buffer
+	s.cap = s.capacityIncrement * ((s.cap + s.capacityIncrement) / s.capacityIncrement)
+	s.buffer = reflect.New(reflect.ArrayOf(int(s.cap), s.typeOf)).Elem()
+	s.bufferAddress = s.buffer.Addr().UnsafePointer()
+	reflect.Copy(s.buffer, old)
 }
 
 // Remove swap-removes an element
@@ -77,34 +84,34 @@ func (s *storage) Remove(index uint32) bool {
 	o := s.len - 1
 	n := index
 
-	// TODO shrink the underlying data arrays
-	if s.itemSize > 0 && n < o {
-		size := s.itemSize
+	if n == o || s.itemSize == 0 {
+		s.len--
+		return false
+	}
 
-		src := unsafe.Add(s.bufferAddress, uintptr(o)*s.itemSize)
-		dst := unsafe.Add(s.bufferAddress, uintptr(n)*s.itemSize)
+	// TODO shrink the underlying data arrays?
+	size := s.itemSize
 
-		dstSlice := (*[math.MaxInt32]byte)(dst)[:size:size]
-		srcSlice := (*[math.MaxInt32]byte)(src)[:size:size]
+	src := unsafe.Add(s.bufferAddress, uintptr(o)*s.itemSize)
+	dst := unsafe.Add(s.bufferAddress, uintptr(n)*s.itemSize)
 
-		copy(dstSlice, srcSlice)
+	dstSlice := (*[math.MaxInt32]byte)(dst)[:size:size]
+	srcSlice := (*[math.MaxInt32]byte)(src)[:size:size]
 
-		s.len--
-		return true
-	}
+	copy(dstSlice, srcSlice)
 
 	s.len--
-	return false
+	return true
 }
 
 // Set sets the storage at the given index
 func (s *storage) Set(index uint32, value interface{}) unsafe.Pointer {
-	rValue := reflect.ValueOf(value)
 	dst := s.Get(index)
 
 	if s.itemSize == 0 {
 		return dst
 	}
+	rValue := reflect.ValueOf(value)
 
 	var src unsafe.Pointer
 	size := s.itemSize
@@ -118,18 +125,20 @@ func (s *storage) Set(index uint32, value interface{}) unsafe.Pointer {
 	return dst
 }
 
-func (s *storage) setPointer(index uint32, value unsafe.Pointer) {
+func (s *storage) SetPointer(index uint32, value unsafe.Pointer) unsafe.Pointer {
+	dst := s.Get(index)
 	if s.itemSize == 0 {
-		return
+		return dst
 	}
 
-	dst := s.Get(index)
 	size := s.itemSize
 
 	dstSlice := (*[math.MaxInt32]byte)(dst)[:size:size]
 	srcSlice := (*[math.MaxInt32]byte)(value)[:size:size]
 
 	copy(dstSlice, srcSlice)
+
+	return dst
 }
 
 // Zero resets a block of storage
@@ -166,3 +175,74 @@ func toSlice[T any](s storage) []T {
 	}
 	return res
 }
+
+type genericStorage[T any] struct {
+	buffer            []T
+	capacityIncrement uint32
+}
+
+// Init initializes a genericStorage
+func (s *genericStorage[T]) Init(increment int, forStorage bool) {
+	cap := 1
+	if forStorage {
+		cap = increment
+	}
+	s.buffer = make([]T, 0, cap)
+	s.capacityIncrement = uint32(increment)
+}
+
+// Get retrieves an element
+func (s *genericStorage[T]) Get(index uint32) T {
+	return s.buffer[index]
+}
+
+// Set sets an element
+func (s *genericStorage[T]) Set(index uint32, value T) {
+	s.buffer[index] = value
+}
+
+// Add adds an element to the end of the genericStorage
+func (s *genericStorage[T]) Add(value T) (index uint32) {
+	s.extend()
+	s.buffer = append(s.buffer, value)
+	return s.Len() - 1
+}
+
+func (s *genericStorage[T]) extend() {
+	currLen := len(s.buffer)
+	currCap := cap(s.buffer)
+	if currCap > currLen {
+		return
+	}
+
+	inc := int(s.capacityIncrement)
+	old := s.buffer
+	cap := inc * ((currCap + inc) / inc)
+	s.buffer = make([]T, currLen, cap)
+	copy(s.buffer, old)
+}
+
+// Remove swap-removes an element
+func (s *genericStorage[T]) Remove(index uint32) bool {
+	o := len(s.buffer) - 1
+	n := int(index)
+
+	if n == o {
+		s.buffer = s.buffer[:o]
+		return false
+	}
+
+	s.buffer[n] = s.buffer[o]
+	s.buffer = s.buffer[:o]
+	return true
+}
+
+// Len returns the number of items in the genericStorage
+func (s *genericStorage[T]) Len() uint32 {
+	return uint32(len(s.buffer))
+}
+
+// Cap returns the capacity of the genericStorage
+func (s *genericStorage[T]) Cap() uint32 {
+	return uint32(cap(s.buffer))
+}
diff --git a/ecs/types.go b/ecs/types.go
index 96b5551..3b0443c 100644
--- a/ecs/types.go
+++ b/ecs/types.go
@@ -1,9 +1,6 @@
 package ecs
 
-import (
-	"reflect"
-	"unsafe"
-)
+import "reflect"
 
 // Eid is the entity identifier/index type
 type eid uint32
@@ -22,9 +19,3 @@ type componentType struct {
 	ID
 	Type reflect.Type
 }
-
-// componentPointer is a component ID with a pointer in a storage
-type componentPointer struct {
-	ID
-	Pointer unsafe.Pointer
-}
diff --git a/ecs/world.go b/ecs/world.go
index 5ff0ccc..889340e 100644
--- a/ecs/world.go
+++ b/ecs/world.go
@@ -37,9 +37,11 @@ func NewWorld() World {
 
 // FromConfig creates a new [World] from a [Config]
 func FromConfig(conf Config) World {
+	entities := make([]entityIndex, 1, conf.CapacityIncrement)
+	entities[0] = entityIndex{arch: nil, index: 0}
 	w := World{
 		config:     conf,
-		entities:   []entityIndex{{arch: nil, index: 0}},
+		entities:   entities,
 		entityPool: newEntityPool(conf.CapacityIncrement),
 		bitPool:    newBitPool(),
 		registry:   newComponentRegistry(),
@@ -47,7 +49,7 @@ func FromConfig(conf Config) World {
 		locks:      BitMask{},
 		listener:   nil,
 	}
-	w.createArchetype(BitMask{})
+	w.createArchetype(BitMask{}, false)
 	return w
 }
 
@@ -68,7 +70,7 @@ func (w *World) NewEntity(comps ...ID) Entity {
 		arch = w.findOrCreateArchetype(arch, comps, nil)
 	}
 
-	idx := arch.Alloc(entity)
+	idx := arch.Alloc(entity, true)
 	len := len(w.entities)
 	if int(entity.id) == len {
 		if len == cap(w.entities) {
@@ -110,7 +112,7 @@ func (w *World) NewEntityWith(comps ...Component) Entity {
 	arch := w.archetypes.Get(0)
 	arch = w.findOrCreateArchetype(arch, ids, nil)
 
-	idx := arch.Alloc(entity)
+	idx := arch.Alloc(entity, false)
 	len := len(w.entities)
 	if int(entity.id) == len {
 		if len == cap(w.entities) {
@@ -327,25 +329,20 @@ func (w *World) Exchange(entity Entity, add []ID, rem []ID) {
 	}
 
 	oldIDs := oldArch.Components()
-	keepIDs := make([]ID, 0, len(oldIDs))
-	for _, id := range oldIDs {
-		if mask.Get(id) {
-			keepIDs = append(keepIDs, id)
-		}
-	}
 
 	arch := w.findOrCreateArchetype(oldArch, add, rem)
+	newIndex := arch.Alloc(entity, false)
 
-	allComps := make([]componentPointer, 0, len(keepIDs)+len(add))
-	for _, id := range keepIDs {
-		comp := oldArch.Get(index.index, id)
-		allComps = append(allComps, componentPointer{ID: id, Pointer: comp})
+	for _, id := range oldIDs {
+		if mask.Get(id) {
+			comp := oldArch.Get(index.index, id)
+			arch.SetPointer(newIndex, id, comp)
+		}
 	}
 	for _, id := range add {
-		allComps = append(allComps, componentPointer{ID: id, Pointer: nil})
+		arch.Zero(newIndex, id)
 	}
 
-	newIndex := arch.AddPointer(entity, allComps...)
 	swapped := oldArch.Remove(index.index)
 
 	if swapped {
@@ -432,23 +429,25 @@ func (w *World) copyTo(entity Entity, id ID, comp interface{}) unsafe.Pointer {
 func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID) *archetype {
 	curr := start
 	mask := start.Mask
-	for _, id := range rem {
+	maxRem := len(rem) - 1
+	maxAdd := len(add) - 1
+	for i, id := range rem {
 		mask.Set(id, false)
 		if next, ok := curr.GetTransitionRemove(id); ok {
 			curr = next
 		} else {
-			next, _ := w.findOrCreateArchetypeSlow(mask)
+			next, _ := w.findOrCreateArchetypeSlow(mask, i == maxRem && maxAdd == 0)
 			next.SetTransitionAdd(id, curr)
 			curr.SetTransitionRemove(id, next)
 			curr = next
 		}
 	}
-	for _, id := range add {
+	for i, id := range add {
 		mask.Set(id, true)
 		if next, ok := curr.GetTransitionAdd(id); ok {
 			curr = next
 		} else {
-			next, _ := w.findOrCreateArchetypeSlow(mask)
+			next, _ := w.findOrCreateArchetypeSlow(mask, i == maxAdd)
 			next.SetTransitionRemove(id, curr)
 			curr.SetTransitionAdd(id, next)
 			curr = next
@@ -457,11 +456,11 @@ func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID) *arc
 	return curr
 }
 
-func (w *World) findOrCreateArchetypeSlow(mask BitMask) (*archetype, bool) {
+func (w *World) findOrCreateArchetypeSlow(mask BitMask, forStorage bool) (*archetype, bool) {
 	if arch, ok := w.findArchetype(mask); ok {
 		return arch, false
 	}
-	return w.createArchetype(mask), true
+	return w.createArchetype(mask, forStorage), true
 }
 
 func (w *World) findArchetype(mask BitMask) (*archetype, bool) {
@@ -475,7 +474,7 @@ func (w *World) findArchetype(mask BitMask) (*archetype, bool) {
 	return nil, false
 }
 
-func (w *World) createArchetype(mask BitMask) *archetype {
+func (w *World) createArchetype(mask BitMask, forStorage bool) *archetype {
 	count := int(mask.TotalBitsSet())
 	types := make([]componentType, count)
 
@@ -489,7 +488,7 @@ func (w *World) createArchetype(mask BitMask) *archetype {
 	}
 	w.archetypes.Add(archetype{})
 	arch := w.archetypes.Get(w.archetypes.Len() - 1)
-	arch.Init(w.config.CapacityIncrement, types...)
+	arch.Init(w.config.CapacityIncrement, forStorage, types...)
 	return arch
 }
 
diff --git a/examples/generic/main.go b/examples/generic/main.go
index 852fba7..7e89761 100644
--- a/examples/generic/main.go
+++ b/examples/generic/main.go
@@ -61,7 +61,7 @@ func main() {
 		// Iterate it.
 		for query.Next() {
 			// Component access through the Query.
-			_, pos, vel := query.Get()
+			pos, vel := query.Get()
 			// Update component fields.
 			pos.X += vel.X
 			pos.Y += vel.Y
@@ -79,7 +79,7 @@ func main() {
 	q := filter.Query(&world)
 
 	for q.Next() {
-		_, pos, vel := q.Get()
+		pos, vel := q.Get()
 		pos.X += vel.X
 		pos.Y += vel.Y
 	}
diff --git a/examples/locked_world/main.go b/examples/locked_world/main.go
index 1b2f9f9..a70d555 100644
--- a/examples/locked_world/main.go
+++ b/examples/locked_world/main.go
@@ -66,7 +66,8 @@ func main() {
 		// Thus, it is not possible to add or remove entities or components.
 		for query.Next() {
 			// Component access through the Query.
-			entity, pos, ene := query.Get()
+			entity := query.Entity()
+			pos, ene := query.Get()
 			// Do model logic.
 			ene.Value += rand.NormFloat64() * 0.05
 
diff --git a/examples/readme/main.go b/examples/readme/main.go
index 8079ba6..037b4be 100644
--- a/examples/readme/main.go
+++ b/examples/readme/main.go
@@ -51,7 +51,7 @@ func main() {
 		// Iterate it
 		for query.Next() {
 			// Component access through the Query.
-			_, pos, vel := query.Get()
+			pos, vel := query.Get()
 			// Update component fields.
 			pos.X += vel.X
 			pos.Y += vel.Y
diff --git a/generic/generate/query.go.txt b/generic/generate/query.go.txt
index e8df288..9d60731 100644
--- a/generic/generate/query.go.txt
+++ b/generic/generate/query.go.txt
@@ -68,7 +68,11 @@ func (q *Filter{{ .Index }}{{ .Types }}) Filter(w *ecs.World) ecs.MaskPair {
 // Create it with [NewFilter{{ .Index }}] and [Filter{{ .Index }}.Query]
 type Query{{ .Index }}{{ .TypesFull }} query
 
-// Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query{{ .Index }}{{ .Types }}) Get() (ecs.Entity, {{ .TypesReturn }}) {
-	return q.Entity(){{if .ReturnAll}}, {{ .ReturnAll }}{{ end }}
+{{if .ReturnAll}}
+// Get returns all queried components for the current query iterator position.
+//
+// Use [Query{{ .Index }}.Entity] to get the current Entity.
+func (q *Query{{ .Index }}{{ .Types }}) Get() ({{ .TypesReturn }}) {
+	return {{ .ReturnAll }}
 }
+{{ end }}
\ No newline at end of file
diff --git a/generic/query_generated.go b/generic/query_generated.go
index 1b8a9ec..b6c73b7 100644
--- a/generic/query_generated.go
+++ b/generic/query_generated.go
@@ -62,11 +62,6 @@ func (q *Filter0) Filter(w *ecs.World) ecs.MaskPair {
 // Create it with [NewFilter0] and [Filter0.Query]
 type Query0 query
 
-// Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query0) Get() ecs.Entity {
-	return q.Entity()
-}
-
 //////////////////////////////////////////////////////////////////////////
 
 // Filter1 is a helper for building [Query1] query iterators.
@@ -135,8 +130,8 @@ func (q *Filter1[A]) Filter(w *ecs.World) ecs.MaskPair {
 type Query1[A any] query
 
 // Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query1[A]) Get() (ecs.Entity, *A) {
-	return q.Entity(), (*A)(q.Query.Get(q.ids[0]))
+func (q *Query1[A]) Get() *A {
+	return (*A)(q.Query.Get(q.ids[0]))
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -207,8 +202,8 @@ func (q *Filter2[A, B]) Filter(w *ecs.World) ecs.MaskPair {
 type Query2[A any, B any] query
 
 // Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query2[A, B]) Get() (ecs.Entity, *A, *B) {
-	return q.Entity(), (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1]))
+func (q *Query2[A, B]) Get() (*A, *B) {
+	return (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1]))
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -279,8 +274,8 @@ func (q *Filter3[A, B, C]) Filter(w *ecs.World) ecs.MaskPair {
 type Query3[A any, B any, C any] query
 
 // Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query3[A, B, C]) Get() (ecs.Entity, *A, *B, *C) {
-	return q.Entity(), (*A)(q.Query.Get(q.ids[0])), (*B)(q.Query.Get(q.ids[1])), (*C)(q.Query.Get(q.ids[2]))
+func (q *Query3[A, B, C]) Get() (*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]))
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -351,8 +346,8 @@ func (q *Filter4[A, B, C, D]) Filter(w *ecs.World) ecs.MaskPair {
 type Query4[A any, B any, C any, D any] query
 
 // Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query4[A, B, C, D]) Get() (ecs.Entity, *A, *B, *C, *D) {
-	return q.Entity(), (*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]))
+func (q *Query4[A, B, C, D]) Get() (*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]))
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -423,8 +418,8 @@ func (q *Filter5[A, B, C, D, E]) Filter(w *ecs.World) ecs.MaskPair {
 type Query5[A any, B any, C any, D any, E any] query
 
 // Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query5[A, B, C, D, E]) Get() (ecs.Entity, *A, *B, *C, *D, *E) {
-	return q.Entity(), (*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]))
+func (q *Query5[A, B, C, D, E]) Get() (*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]))
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -495,8 +490,8 @@ func (q *Filter6[A, B, C, D, E, F]) Filter(w *ecs.World) ecs.MaskPair {
 type Query6[A any, B any, C any, D any, E any, F any] query
 
 // Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query6[A, B, C, D, E, F]) Get() (ecs.Entity, *A, *B, *C, *D, *E, *F) {
-	return q.Entity(), (*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]))
+func (q *Query6[A, B, C, D, E, F]) Get() (*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]))
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -567,8 +562,8 @@ func (q *Filter7[A, B, C, D, E, F, G]) Filter(w *ecs.World) ecs.MaskPair {
 type Query7[A any, B any, C any, D any, E any, F any, G any] query
 
 // Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query7[A, B, C, D, E, F, G]) Get() (ecs.Entity, *A, *B, *C, *D, *E, *F, *G) {
-	return q.Entity(), (*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]))
+func (q *Query7[A, B, C, D, E, F, G]) Get() (*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]))
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -639,6 +634,6 @@ func (q *Filter8[A, B, C, D, E, F, G, H]) Filter(w *ecs.World) ecs.MaskPair {
 type Query8[A any, B any, C any, D any, E any, F any, G any, H any] query
 
 // Get returns the [ecs.Entity] and all queried components for the current query iterator position.
-func (q *Query8[A, B, C, D, E, F, G, H]) Get() (ecs.Entity, *A, *B, *C, *D, *E, *F, *G, *H) {
-	return q.Entity(), (*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]))
+func (q *Query8[A, B, C, D, E, F, G, H]) Get() (*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]))
 }


Test patch:
diff --git a/benchmark/iterate/arche_test.go b/benchmark/iterate/arche_test.go
index 767c476..013c06b 100644
--- a/benchmark/iterate/arche_test.go
+++ b/benchmark/iterate/arche_test.go
@@ -73,7 +73,7 @@ func runArcheQueryGeneric(b *testing.B, count int) {
 		q := query.Query(&world)
 		b.StartTimer()
 		for q.Next() {
-			_, pos := q.Get()
+			pos := q.Get()
 			_ = pos
 		}
 	}
@@ -131,7 +131,7 @@ func runArcheQueryGeneric5C(b *testing.B, count int) {
 		q := query.Query(&world)
 		b.StartTimer()
 		for q.Next() {
-			_, _, _, _, _, _ = q.Get()
+			_, _, _, _, _ = q.Get()
 		}
 	}
 }
diff --git a/benchmark/move/ggecs_test.go b/benchmark/move/ggecs_test.go
new file mode 100644
index 0000000..6750dc6
--- /dev/null
+++ b/benchmark/move/ggecs_test.go
@@ -0,0 +1,51 @@
+package move
+
+import (
+	"testing"
+
+	ecs "github.com/marioolofo/go-gameengine-ecs"
+	c "github.com/mlange-42/arche/benchmark/common"
+)
+
+func runGameEngineEcsMove(b *testing.B, count int, add, rem []ecs.ID) {
+	for i := 0; i < b.N; i++ {
+		b.StopTimer()
+		comps := []ecs.ComponentConfig{
+			{ID: 0, Component: c.TestStruct0{}},
+			{ID: 1, Component: c.TestStruct1{}},
+			{ID: 2, Component: c.TestStruct2{}},
+			{ID: 3, Component: c.TestStruct3{}},
+			{ID: 4, Component: c.TestStruct4{}},
+			{ID: 5, Component: c.TestStruct5{}},
+			{ID: 6, Component: c.TestStruct6{}},
+		}
+		world := ecs.NewWorld(comps...)
+
+		for i := 0; i < count; i++ {
+			entity := world.NewEntity()
+			world.Assign(entity, add...)
+		}
+		filter := world.NewFilter(add...)
+
+		b.StartTimer()
+
+		for _, e := range filter.Entities() {
+			world.Remove(e, rem...)
+		}
+
+	}
+}
+
+func BenchmarkGGECSMove_1C_1_000(b *testing.B) {
+	runGameEngineEcsMove(b, 1000,
+		[]ecs.ID{0},
+		[]ecs.ID{0},
+	)
+}
+
+func BenchmarkGGECSMove_5C_1_000(b *testing.B) {
+	runGameEngineEcsMove(b, 1000,
+		[]ecs.ID{0, 1, 2, 3, 4},
+		[]ecs.ID{0},
+	)
+}
diff --git a/ecs/archetype_test.go b/ecs/archetype_test.go
index e2d1ac5..2837be7 100644
--- a/ecs/archetype_test.go
+++ b/ecs/archetype_test.go
@@ -14,7 +14,7 @@ func TestArchetype(t *testing.T) {
 	}
 
 	arch := archetype{}
-	arch.Init(32, comps...)
+	arch.Init(32, false, comps...)
 
 	arch.Add(
 		newEntity(0),
@@ -66,10 +66,6 @@ func TestArchetype(t *testing.T) {
 			Component{ID: 0, Component: &position{4, 5}},
 		)
 	})
-
-	assert.Panics(t, func() {
-		arch.AddPointer(newEntity(1))
-	})
 }
 
 func TestNewArchetype(t *testing.T) {
@@ -78,7 +74,7 @@ func TestNewArchetype(t *testing.T) {
 		{ID: 1, Type: reflect.TypeOf(rotation{})},
 	}
 	arch := archetype{}
-	arch.Init(32, comps...)
+	arch.Init(32, true, comps...)
 
 	comps = []componentType{
 		{ID: 1, Type: reflect.TypeOf(rotation{})},
@@ -86,7 +82,7 @@ func TestNewArchetype(t *testing.T) {
 	}
 	assert.Panics(t, func() {
 		arch := archetype{}
-		arch.Init(32, comps...)
+		arch.Init(32, true, comps...)
 	})
 }
 
@@ -97,7 +93,7 @@ func BenchmarkArchetypeAccess_1000(b *testing.B) {
 	}
 
 	arch := archetype{}
-	arch.Init(32, comps...)
+	arch.Init(32, true, comps...)
 
 	for i := 0; i < 1000; i++ {
 		arch.Add(
@@ -124,7 +120,7 @@ func BenchmarkArchetypeAccessUnsafe_1000(b *testing.B) {
 	}
 
 	arch := archetype{}
-	arch.Init(32, comps...)
+	arch.Init(32, true, comps...)
 
 	for i := 0; i < 1000; i++ {
 		arch.Add(
diff --git a/ecs/storage_test.go b/ecs/storage_test.go
index 4cc1978..87c1943 100644
--- a/ecs/storage_test.go
+++ b/ecs/storage_test.go
@@ -21,7 +21,11 @@ type simpleStruct struct {
 func TestStorageAddGet(t *testing.T) {
 	obj1 := testStruct{}
 	s := storage{}
-	s.Init(reflect.TypeOf(obj1), 1)
+	s.Init(reflect.TypeOf(obj1), 1, true)
+
+	assert.Equal(t, 1, int(s.Cap()))
+	assert.Equal(t, 0, int(s.Len()))
+
 	storageAddGet(t, s)
 }
 
@@ -54,7 +58,7 @@ func storageAddGet(t *testing.T, s storage) {
 func TestStorageRemove(t *testing.T) {
 	ref := simpleStruct{}
 	s := storage{}
-	s.Init(reflect.TypeOf(ref), 32)
+	s.Init(reflect.TypeOf(ref), 32, false)
 
 	storageRemove(t, s)
 }
@@ -79,7 +83,7 @@ func storageRemove(t *testing.T, s storage) {
 func TestStorageDataSize(t *testing.T) {
 	ref := simpleStruct{}
 	s := storage{}
-	s.Init(reflect.TypeOf(ref), 1)
+	s.Init(reflect.TypeOf(ref), 1, true)
 
 	for i := 0; i < 5; i++ {
 		obj := simpleStruct{i}
@@ -104,28 +108,32 @@ func TestStorageDataSize(t *testing.T) {
 
 func TestNewStorage(t *testing.T) {
 	s := storage{}
-	s.Init(reflect.TypeOf(simpleStruct{}), 32)
+	s.Init(reflect.TypeOf(simpleStruct{}), 32, true)
 }
 
 func TestStoragePointer(t *testing.T) {
 	a := storage{}
-	a.Init(reflect.TypeOf(simpleStruct{}), 32)
+	a.Init(reflect.TypeOf(simpleStruct{}), 32, true)
 	a.Add(&simpleStruct{})
 	s := (*simpleStruct)(a.Get(0))
 	s.Index = 10
 
 	b := storage{}
-	b.Init(reflect.TypeOf(simpleStruct{}), 32)
+	b.Init(reflect.TypeOf(simpleStruct{}), 32, true)
 
 	ptr := a.Get(0)
 	b.AddPointer(ptr)
 	s = (*simpleStruct)(b.Get(0))
 	assert.Equal(t, 10, s.Index)
+
+	a.Set(0, &simpleStruct{20})
+	s = (*simpleStruct)(a.Get(0))
+	assert.Equal(t, 20, s.Index)
 }
 
 func TestStorageZeroSize(t *testing.T) {
 	a := storage{}
-	a.Init(reflect.TypeOf(label{}), 32)
+	a.Init(reflect.TypeOf(label{}), 32, true)
 	a.Add(&label{})
 	a.Add(&label{})
 	s := (*label)(a.Get(0))
@@ -134,7 +142,7 @@ func TestStorageZeroSize(t *testing.T) {
 	assert.NotNil(t, s)
 
 	b := storage{}
-	b.Init(reflect.TypeOf(label{}), 32)
+	b.Init(reflect.TypeOf(label{}), 32, true)
 
 	ptr := a.Get(0)
 	b.AddPointer(ptr)
@@ -146,11 +154,31 @@ func TestStorageZeroSize(t *testing.T) {
 	assert.NotNil(t, s)
 }
 
+func TestGenericStorage(t *testing.T) {
+	s := genericStorage[Entity]{}
+	s.Init(32, false)
+
+	assert.Equal(t, 1, int(s.Cap()))
+	assert.Equal(t, 0, int(s.Len()))
+
+	s.Add(Entity{})
+	s.Add(Entity{})
+
+	assert.Equal(t, 32, int(s.Cap()))
+	assert.Equal(t, 2, int(s.Len()))
+
+	s.Set(0, Entity{1, 2})
+	assert.Equal(t, Entity{1, 2}, s.Get(0))
+	s.Remove(0)
+
+	assert.Equal(t, Entity{}, s.Get(0))
+}
+
 func BenchmarkIterStorage_1000(b *testing.B) {
 	b.StopTimer()
 	ref := testStruct{}
 	s := storage{}
-	s.Init(reflect.TypeOf(ref), 128)
+	s.Init(reflect.TypeOf(ref), 128, true)
 	for i := 0; i < 1000; i++ {
 		s.Add(&testStruct{})
 	}
@@ -205,7 +233,7 @@ func BenchmarkAddStorage_1000(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		b.StopTimer()
 		s := storage{}
-		s.Init(reflect.TypeOf(ref), 1024)
+		s.Init(reflect.TypeOf(ref), 1024, true)
 		b.StartTimer()
 
 		for i := 0; i < 1000; i++ {
@@ -253,7 +281,7 @@ func BenchmarkRemoveStorage_1000(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		b.StopTimer()
 		s := storage{}
-		s.Init(reflect.TypeOf(ref), 1024)
+		s.Init(reflect.TypeOf(ref), 1024, true)
 		for i := 0; i < 1000; i++ {
 			s.Add(&ref)
 		}
diff --git a/generic/query_test.go b/generic/query_test.go
index 2369e33..afd79ac 100644
--- a/generic/query_test.go
+++ b/generic/query_test.go
@@ -98,7 +98,7 @@ func TestQuery0(t *testing.T) {
 	assert.Equal(t, ecs.All(8).Without(9), filter.Filter(&w))
 	query := filter.Query(&w)
 	for query.Next() {
-		_ = query.Get()
+		_ = query.Entity()
 		cnt++
 	}
 	assert.Equal(t, 1, cnt)
@@ -133,7 +133,7 @@ func TestQuery1(t *testing.T) {
 	assert.Equal(t, ecs.All(0, 8).Without(9), filter.Filter(&w))
 	query := filter.Query(&w)
 	for query.Next() {
-		_, c0 := query.Get()
+		c0 := query.Get()
 		assert.Equal(t, cnt+1, int(c0.val))
 		cnt++
 	}
@@ -173,7 +173,7 @@ func TestQuery2(t *testing.T) {
 		cnt := 0
 		q := filter.Query(&w)
 		for q.Next() {
-			_, c1, c2 := q.Get()
+			c1, c2 := q.Get()
 			assert.Equal(t, cnt+1, int(c1.val))
 			assert.Equal(t, cnt+2, int(c2.val))
 			cnt++
@@ -217,7 +217,7 @@ func TestQuery3(t *testing.T) {
 	assert.Equal(t, ecs.All(0, 2, 8).Without(9), filter.Filter(&w))
 	query := filter.Query(&w)
 	for query.Next() {
-		_, c1, c2, c3 := query.Get()
+		c1, c2, c3 := query.Get()
 		assert.Equal(t, cnt+1, int(c1.val))
 		assert.Equal(t, cnt+2, int(c2.val))
 		assert.Equal(t, cnt+3, int(c3.val))
@@ -265,7 +265,7 @@ func TestQuery4(t *testing.T) {
 	assert.Equal(t, ecs.All(0, 2, 3, 8).Without(9), filter.Filter(&w))
 	query := filter.Query(&w)
 	for query.Next() {
-		_, c1, c2, c3, c4 := query.Get()
+		c1, c2, c3, c4 := query.Get()
 		assert.Equal(t, cnt+1, int(c1.val))
 		assert.Equal(t, cnt+2, int(c2.val))
 		assert.Equal(t, cnt+3, int(c3.val))
@@ -318,7 +318,7 @@ func TestQuery5(t *testing.T) {
 	assert.Equal(t, ecs.All(0, 2, 3, 4, 8).Without(9), filter.Filter(&w))
 	query := filter.Query(&w)
 	for query.Next() {
-		_, c1, c2, c3, c4, c5 := query.Get()
+		c1, c2, c3, c4, c5 := query.Get()
 		assert.Equal(t, cnt+1, int(c1.val))
 		assert.Equal(t, cnt+2, int(c2.val))
 		assert.Equal(t, cnt+3, int(c3.val))
@@ -376,7 +376,7 @@ func TestQuery6(t *testing.T) {
 	assert.Equal(t, ecs.All(0, 2, 3, 4, 5, 8).Without(9), filter.Filter(&w))
 	query := filter.Query(&w)
 	for query.Next() {
-		_, c1, c2, c3, c4, c5, c6 := query.Get()
+		c1, c2, c3, c4, c5, c6 := query.Get()
 		assert.Equal(t, cnt+1, int(c1.val))
 		assert.Equal(t, cnt+2, int(c2.val))
 		assert.Equal(t, cnt+3, int(c3.val))
@@ -442,7 +442,7 @@ func TestQuery7(t *testing.T) {
 	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, c2, c3, c4, c5, c6, c7 := query.Get()
+		c1, c2, c3, c4, c5, c6, c7 := query.Get()
 		assert.Equal(t, cnt+1, int(c1.val))
 		assert.Equal(t, cnt+2, int(c2.val))
 		assert.Equal(t, cnt+3, int(c3.val))
@@ -505,7 +505,7 @@ func TestQuery8(t *testing.T) {
 	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, c2, c3, c4, c5, c6, c7, c8 := query.Get()
+		c1, c2, c3, c4, c5, c6, c7, c8 := query.Get()
 		assert.Equal(t, cnt+1, int(c1.val))
 		assert.Equal(t, cnt+2, int(c2.val))
 		assert.Equal(t, cnt+3, int(c3.val))
@@ -535,7 +535,7 @@ func TestQueryGeneric(t *testing.T) {
 	q := query.Query(&world)
 	cnt := 0
 	for q.Next() {
-		_, s1, s2 := q.Get()
+		s1, s2 := q.Get()
 		_ = s1
 		_ = s2
 		cnt++