Repository: mlange-42/arche
Pull request #283 (2023-05-09 21:34:14)

Use `uint32` instead of `uintptr` for indices and query iteration counters

coveralls: ## Pull Request Test Coverage Report for [Build 4930694381](https://coveralls.io/builds/59799854)

* **13** of **13**   **(100.0%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **100.0%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/59799854/badge)](https://coveralls.io/builds/59799854) |
| :-- | --: |
| Change from base [Build 4916561474](https://coveralls.io/builds/59735249): |  0.0% |
| Covered Lines: | 3757 |
| Relevant Lines: | 3757 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 425aafa..f88b7be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@
 * Speed up generic query re-compilation after changing the relation target (#255)
 * Speed up archetype and node iteration to be as fast as before the new nested structure (#270)
 * Filter cache stores archetype graph nodes instead of archetypes (#276)
+* Use `uint32` instead of `uintptr` for indices and query iteration counter (#283)
 
 ### Documentation
 
diff --git a/ecs/archetype.go b/ecs/archetype.go
index 7df4e80..aa66a03 100644
--- a/ecs/archetype.go
+++ b/ecs/archetype.go
@@ -9,7 +9,7 @@ import (
 )
 
 // layoutSize is the size of an archetype column layout in bytes.
-var layoutSize = unsafe.Sizeof(layout{})
+var layoutSize uint32 = uint32(unsafe.Sizeof(layout{}))
 
 // Helper for accessing data from an archetype
 type archetypeAccess struct {
@@ -21,12 +21,12 @@ type archetypeAccess struct {
 }
 
 // GetEntity returns the entity at the given index
-func (a *archetypeAccess) GetEntity(index uintptr) Entity {
+func (a *archetypeAccess) GetEntity(index uint32) Entity {
 	return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
 }
 
 // Get returns the component with the given ID at the given index
-func (a *archetypeAccess) Get(index uintptr, id ID) unsafe.Pointer {
+func (a *archetypeAccess) Get(index uint32, id ID) unsafe.Pointer {
 	return a.getLayout(id).Get(index)
 }
 
@@ -42,17 +42,17 @@ func (a *archetypeAccess) HasRelation() bool {
 
 // GetLayout returns the column layout for a component.
 func (a *archetypeAccess) getLayout(id ID) *layout {
-	return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
+	return (*layout)(unsafe.Add(a.basePointer, layoutSize*uint32(id)))
 }
 
 // layout specification of a component column.
 type layout struct {
 	pointer  unsafe.Pointer // Pointer to the first element in the component column.
-	itemSize uintptr        // Component/step size
+	itemSize uint32         // Component/step size
 }
 
 // Get returns a pointer to the item at the given index.
-func (l *layout) Get(index uintptr) unsafe.Pointer {
+func (l *layout) Get(index uint32) unsafe.Pointer {
 	if l.pointer == nil {
 		return nil
 	}
@@ -96,7 +96,7 @@ func (a *archetype) Init(node *archNode, index int32, forStorage bool, relation
 		a.buffers[i] = reflect.New(reflect.ArrayOf(cap, tp)).Elem()
 		a.layouts[id] = layout{
 			a.buffers[i].Addr().UnsafePointer(),
-			size,
+			uint32(size),
 		}
 		a.indices.Set(id, uint32(i))
 	}
@@ -117,8 +117,8 @@ func (a *archetype) Init(node *archNode, index int32, forStorage bool, relation
 }
 
 // Add adds an entity with optionally zeroed components to the archetype
-func (a *archetype) Alloc(entity Entity) uintptr {
-	idx := uintptr(a.len)
+func (a *archetype) Alloc(entity Entity) uint32 {
+	idx := a.len
 	a.extend(1)
 	a.addEntity(idx, &entity)
 	a.len++
@@ -132,11 +132,11 @@ func (a *archetype) AllocN(count uint32) {
 }
 
 // Add adds an entity with components to the archetype.
-func (a *archetype) Add(entity Entity, components ...Component) uintptr {
+func (a *archetype) Add(entity Entity, components ...Component) uint32 {
 	if len(components) != len(a.node.Ids) {
 		panic("Invalid number of components")
 	}
-	idx := uintptr(a.len)
+	idx := a.len
 
 	a.extend(1)
 	a.addEntity(idx, &entity)
@@ -147,7 +147,7 @@ func (a *archetype) Add(entity Entity, components ...Component) uintptr {
 			continue
 		}
 		src := reflect.ValueOf(c.Comp).UnsafePointer()
-		dst := a.Get(uintptr(idx), c.ID)
+		dst := a.Get(idx, c.ID)
 		a.copy(src, dst, size)
 	}
 	a.len++
@@ -158,10 +158,10 @@ func (a *archetype) Add(entity Entity, components ...Component) uintptr {
 //
 // Performs a swap-remove and reports whether a swap was necessary
 // (i.e. not the last entity that was removed).
-func (a *archetype) Remove(index uintptr) bool {
+func (a *archetype) Remove(index uint32) bool {
 	swapped := a.removeEntity(index)
 
-	old := uintptr(a.len - 1)
+	old := a.len - 1
 
 	if index != old {
 		for _, id := range a.node.Ids {
@@ -182,14 +182,14 @@ func (a *archetype) Remove(index uintptr) bool {
 }
 
 // ZeroAll resets a block of storage in all buffers.
-func (a *archetype) ZeroAll(index uintptr) {
+func (a *archetype) ZeroAll(index uint32) {
 	for _, id := range a.node.Ids {
 		a.Zero(index, id)
 	}
 }
 
 // ZeroAll resets a block of storage in one buffer.
-func (a *archetype) Zero(index uintptr, id ID) {
+func (a *archetype) Zero(index uint32, id ID) {
 	lay := a.getLayout(id)
 	size := lay.itemSize
 	if size == 0 {
@@ -200,12 +200,12 @@ func (a *archetype) Zero(index uintptr, id ID) {
 }
 
 // SetEntity overwrites an entity
-func (a *archetype) SetEntity(index uintptr, entity Entity) {
+func (a *archetype) SetEntity(index uint32, entity Entity) {
 	a.addEntity(index, &entity)
 }
 
 // Set overwrites a component with the data behind the given pointer
-func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
+func (a *archetype) Set(index uint32, id ID, comp interface{}) unsafe.Pointer {
 	lay := a.getLayout(id)
 	dst := a.Get(index, id)
 	size := lay.itemSize
@@ -220,7 +220,7 @@ func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
 }
 
 // SetPointer overwrites a component with the data behind the given pointer
-func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
+func (a *archetype) SetPointer(index uint32, id ID, comp unsafe.Pointer) unsafe.Pointer {
 	lay := a.getLayout(id)
 	dst := a.Get(index, id)
 	size := lay.itemSize
@@ -315,7 +315,7 @@ func (a *archetype) UpdateStats(node *stats.NodeStats, stats *stats.ArchetypeSta
 }
 
 // copy from one pointer to another.
-func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
+func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uint32) {
 	dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
 	srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
 	copy(dstSlice, srcSlice)
@@ -348,7 +348,7 @@ func (a *archetype) extend(by uint32) {
 }
 
 // Adds an entity at the given index. Does not extend the entity buffer.
-func (a *archetype) addEntity(index uintptr, entity *Entity) {
+func (a *archetype) addEntity(index uint32, entity *Entity) {
 	dst := unsafe.Add(a.entityPointer, entitySize*index)
 	src := unsafe.Pointer(entity)
 	a.copy(src, dst, entitySize)
@@ -356,8 +356,8 @@ func (a *archetype) addEntity(index uintptr, entity *Entity) {
 
 // removeEntity removes an entity from tne archetype.
 // Components need to be removed separately.
-func (a *archetype) removeEntity(index uintptr) bool {
-	old := uintptr(a.len - 1)
+func (a *archetype) removeEntity(index uint32) bool {
+	old := a.len - 1
 
 	if index == old {
 		return false
diff --git a/ecs/entity.go b/ecs/entity.go
index 029b29d..685d620 100644
--- a/ecs/entity.go
+++ b/ecs/entity.go
@@ -6,10 +6,10 @@ import "reflect"
 var entityType = reflect.TypeOf(Entity{})
 
 // Size of an [Entity] in memory, in bytes.
-var entitySize = entityType.Size()
+var entitySize uint32 = uint32(entityType.Size())
 
 // Size of an [entityIndex] in memory.
-var entityIndexSize = reflect.TypeOf(entityIndex{}).Size()
+var entityIndexSize uint32 = uint32(reflect.TypeOf(entityIndex{}).Size())
 
 // Entity identifier.
 // Holds an entity ID and it's generation for recycling.
@@ -41,5 +41,5 @@ func (e Entity) IsZero() bool {
 // entityIndex indicates where an entity is currently stored.
 type entityIndex struct {
 	arch  *archetype // Entity's current archetype
-	index uintptr    // Entity's current index in the archetype
+	index uint32     // Entity's current index in the archetype
 }
diff --git a/ecs/query.go b/ecs/query.go
index b476196..2f20c4e 100644
--- a/ecs/query.go
+++ b/ecs/query.go
@@ -18,8 +18,8 @@ type Query struct {
 	nodes          []*archNode      // The query's nodes
 	world          *World           // The [World].
 	access         *archetypeAccess // Access helper for the archetype currently being iterated.
-	entityIndex    uintptr          // Iteration index of the current [Entity] current archetype.
-	entityIndexMax uintptr          // Maximum entity index in the current archetype.
+	entityIndex    uint32           // Iteration index of the current [Entity] current archetype.
+	entityIndexMax uint32           // Maximum entity index in the current archetype.
 	archIndex      int32            // Iteration index of the current archetype.
 	nodeIndex      int32            // Iteration index of the current archetype.
 	count          int32            // Cached entity count.
@@ -57,8 +57,8 @@ func newArchQuery(world *World, lockBit uint8, archetype *batchArchetype) Query
 			archIndex:      0,
 			lockBit:        lockBit,
 			count:          int32(archetype.EndIndex - archetype.StartIndex),
-			entityIndex:    uintptr(archetype.StartIndex - 1),
-			entityIndexMax: uintptr(archetype.EndIndex - 1),
+			entityIndex:    archetype.StartIndex - 1,
+			entityIndexMax: archetype.EndIndex - 1,
 		}
 	}
 	return Query{
@@ -195,7 +195,7 @@ func (q *Query) nextArchetypeBatch() bool {
 			q.access = &a.archetypeAccess
 			q.entityIndex = 0
 			batch := q.archetypes.(*batchArchetype)
-			q.entityIndexMax = uintptr(batch.EndIndex) - 1
+			q.entityIndexMax = batch.EndIndex - 1
 			return true
 		}
 	}
@@ -211,7 +211,7 @@ func (q *Query) nextArchetypeSimple() bool {
 		if aLen > 0 {
 			q.access = &a.archetypeAccess
 			q.entityIndex = 0
-			q.entityIndexMax = uintptr(aLen) - 1
+			q.entityIndexMax = aLen - 1
 			return true
 		}
 	}
@@ -253,7 +253,7 @@ func (q *Query) nextNodeFilter() bool {
 			arch := arches.Get(0)
 			archLen := arch.Len()
 			if archLen > 0 {
-				q.setArchetype(nil, &arch.archetypeAccess, arch.index, uintptr(archLen)-1)
+				q.setArchetype(nil, &arch.archetypeAccess, arch.index, archLen-1)
 				return true
 			}
 			continue
@@ -262,7 +262,7 @@ func (q *Query) nextNodeFilter() bool {
 		if rf, ok := q.filter.(*relationFilter); ok {
 			target := rf.Target
 			if arch, ok := n.archetypeMap[target]; ok && arch.Len() > 0 {
-				q.setArchetype(nil, &arch.archetypeAccess, arch.index, uintptr(arch.Len())-1)
+				q.setArchetype(nil, &arch.archetypeAccess, arch.index, arch.Len()-1)
 				return true
 			}
 			continue
@@ -291,7 +291,7 @@ func (q *Query) nextNodeAll() bool {
 			arch := arches.Get(0)
 			archLen := arch.Len()
 			if archLen > 0 {
-				q.setArchetype(nil, &arch.archetypeAccess, arch.index, uintptr(archLen)-1)
+				q.setArchetype(nil, &arch.archetypeAccess, arch.index, archLen-1)
 				return true
 			}
 			continue
@@ -300,7 +300,7 @@ func (q *Query) nextNodeAll() bool {
 		if rf, ok := q.filter.(*relationFilter); ok {
 			target := rf.Target
 			if arch, ok := n.archetypeMap[target]; ok && arch.Len() > 0 {
-				q.setArchetype(nil, &arch.archetypeAccess, arch.index, uintptr(arch.Len())-1)
+				q.setArchetype(nil, &arch.archetypeAccess, arch.index, arch.Len()-1)
 				return true
 			}
 			continue
@@ -316,7 +316,7 @@ func (q *Query) nextNodeAll() bool {
 	return false
 }
 
-func (q *Query) setArchetype(arches archetypes, access *archetypeAccess, archIndex int32, maxIndex uintptr) {
+func (q *Query) setArchetype(arches archetypes, access *archetypeAccess, archIndex int32, maxIndex uint32) {
 	q.archetypes = arches
 	q.archIndex = archIndex
 	q.access = access
@@ -325,7 +325,7 @@ func (q *Query) setArchetype(arches archetypes, access *archetypeAccess, archInd
 }
 
 func (q *Query) stepArchetype(step uint32) (int, bool) {
-	q.entityIndex += uintptr(step)
+	q.entityIndex += step
 	if q.entityIndex <= q.entityIndexMax {
 		return 0, true
 	}
diff --git a/ecs/world.go b/ecs/world.go
index 87a57f7..5979559 100644
--- a/ecs/world.go
+++ b/ecs/world.go
@@ -265,7 +265,7 @@ func (w *World) newEntities(count int, targetID int8, target Entity, comps ...ID
 		var i uint32
 		for i = 0; i < cnt; i++ {
 			idx := startIdx + i
-			entity := arch.GetEntity(uintptr(idx))
+			entity := arch.GetEntity(idx)
 			w.listener(&EntityEvent{entity, Mask{}, arch.Mask, comps, nil, arch.node.Ids, 1, Entity{}, arch.RelationTarget, false})
 		}
 	}
@@ -296,7 +296,7 @@ func (w *World) newEntitiesWith(count int, targetID int8, target Entity, comps .
 		cnt := uint32(count)
 		for i = 0; i < cnt; i++ {
 			idx := startIdx + i
-			entity := arch.GetEntity(uintptr(idx))
+			entity := arch.GetEntity(idx)
 			w.listener(&EntityEvent{entity, Mask{}, arch.Mask, ids, nil, arch.node.Ids, 1, Entity{}, arch.RelationTarget, false})
 		}
 	}
@@ -368,7 +368,7 @@ func (w *World) removeEntities(filter Filter) int {
 
 	lock := w.lock()
 
-	var count uintptr
+	var count uint32
 
 	arches := w.getArchetypes(filter)
 	numArches := int32(len(arches))
@@ -376,10 +376,10 @@ func (w *World) removeEntities(filter Filter) int {
 	for i = 0; i < numArches; i++ {
 		arch := arches[i]
 
-		len := uintptr(arch.Len())
+		len := arch.Len()
 		count += len
 
-		var j uintptr
+		var j uint32
 		for j = 0; j < len; j++ {
 			entity := arch.GetEntity(j)
 			if w.listener != nil {
@@ -684,18 +684,18 @@ func (w *World) exchangeArch(oldArch *archetype, oldArchLen uint32, add []ID, re
 	oldIDs := oldArch.Components()
 	arch := w.findOrCreateArchetype(oldArch, add, rem, oldArch.RelationTarget)
 
-	startIdx := uintptr(arch.Len())
-	count := uintptr(oldArchLen)
+	startIdx := arch.Len()
+	count := oldArchLen
 	arch.AllocN(uint32(count))
 
-	var i uintptr
+	var i uint32
 	for i = 0; i < count; i++ {
 		idx := startIdx + i
 		entity := oldArch.GetEntity(i)
 		index := &w.entities[entity.id]
-		arch.SetEntity(uintptr(idx), entity)
+		arch.SetEntity(idx, entity)
 		index.arch = arch
-		index.index = uintptr(idx)
+		index.index = idx
 
 		for _, id := range oldIDs {
 			if mask.Get(id) {
@@ -712,7 +712,7 @@ func (w *World) exchangeArch(oldArch *archetype, oldArchLen uint32, add []ID, re
 	oldArch.Reset()
 	w.cleanupArchetype(oldArch)
 
-	return arch, uint32(startIdx)
+	return arch, startIdx
 }
 
 // getRelation returns the target entity for an entity relation.
@@ -850,18 +850,18 @@ func (w *World) setRelationArch(oldArch *archetype, oldArchLen uint32, comp ID,
 		arch = w.createArchetype(oldArch.node, target, true)
 	}
 
-	startIdx := uintptr(arch.Len())
-	count := uintptr(oldArchLen)
-	arch.AllocN(uint32(count))
+	startIdx := arch.Len()
+	count := oldArchLen
+	arch.AllocN(count)
 
-	var i uintptr
+	var i uint32
 	for i = 0; i < count; i++ {
 		idx := startIdx + i
 		entity := oldArch.GetEntity(i)
 		index := &w.entities[entity.id]
-		arch.SetEntity(uintptr(idx), entity)
+		arch.SetEntity(idx, entity)
 		index.arch = arch
-		index.index = uintptr(idx)
+		index.index = idx
 
 		for _, id := range oldIDs {
 			comp := oldArch.Get(i, id)
@@ -1141,7 +1141,7 @@ func (w *World) newEntitiesWithNoNotify(count int, targetID int8, target Entity,
 	var i uint32
 	for i = 0; i < cnt; i++ {
 		idx := startIdx + i
-		entity := arch.GetEntity(uintptr(idx))
+		entity := arch.GetEntity(idx)
 		for _, c := range comps {
 			w.copyTo(entity, c.ID, c.Comp)
 		}
@@ -1174,7 +1174,7 @@ func (w *World) createEntity(arch *archetype) Entity {
 // createEntity creates multiple Entities and adds them to the given archetype.
 func (w *World) createEntities(arch *archetype, count uint32) {
 	startIdx := arch.Len()
-	arch.AllocN(uint32(count))
+	arch.AllocN(count)
 
 	len := len(w.entities)
 	required := len + int(count) - w.entityPool.Available()
@@ -1192,8 +1192,8 @@ func (w *World) createEntities(arch *archetype, count uint32) {
 	for i = 0; i < count; i++ {
 		idx := startIdx + i
 		entity := w.entityPool.Get()
-		arch.SetEntity(uintptr(idx), entity)
-		w.entities[entity.id] = entityIndex{arch: arch, index: uintptr(idx)}
+		arch.SetEntity(idx, entity)
+		w.entities[entity.id] = entityIndex{arch: arch, index: idx}
 		w.targetEntities.Set(entity.id, false)
 	}
 }
@@ -1425,7 +1425,7 @@ func (w *World) closeQuery(query *Query) {
 // notifies the listener for all entities on a batch query.
 func (w *World) notifyQuery(batchArch *batchArchetype) {
 	arch := batchArch.Archetype
-	var i uintptr
+	var i uint32
 
 	event := EntityEvent{
 		Entity{}, Mask{}, arch.Mask, batchArch.Added, batchArch.Removed, arch.node.Ids, 1,
@@ -1440,7 +1440,7 @@ func (w *World) notifyQuery(batchArch *batchArchetype) {
 		event.TargetChanged = event.OldMask == event.NewMask
 	}
 
-	start, end := uintptr(batchArch.StartIndex), uintptr(batchArch.EndIndex)
+	start, end := batchArch.StartIndex, batchArch.EndIndex
 	for i = start; i < end; i++ {
 		entity := arch.GetEntity(i)
 		event.Entity = entity


Test patch:
diff --git a/ecs/archetype_test.go b/ecs/archetype_test.go
index 34da468..8456067 100644
--- a/ecs/archetype_test.go
+++ b/ecs/archetype_test.go
@@ -267,9 +267,9 @@ func BenchmarkIterArchetype_1000(b *testing.B) {
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		len := uintptr(arch.Len())
+		len := arch.Len()
 		id := ID(0)
-		var j uintptr
+		var j uint32
 		for j = 0; j < len; j++ {
 			pos := (*testStruct0)(arch.Get(j, id))
 			pos.Val++
diff --git a/ecs/benchmarks_test.go b/ecs/benchmarks_test.go
new file mode 100644
index 0000000..3f0bbf0
--- /dev/null
+++ b/ecs/benchmarks_test.go
@@ -0,0 +1,65 @@
+package ecs_test
+
+import "testing"
+
+func BenchmarkSimpleIterUInt32_1000(b *testing.B) {
+	var iMax uint32 = 1000
+	var sum uint32 = 0
+	for i := 0; i < b.N; i++ {
+		var i uint32 = 0
+		for i < iMax {
+			i++
+			sum += i
+		}
+	}
+}
+
+func BenchmarkSimpleIterUInt32_Convert_1000(b *testing.B) {
+	var iMax uint32 = 1000
+	var sum uintptr = 0
+	for i := 0; i < b.N; i++ {
+		var i uint32 = 0
+		for i < iMax {
+			i++
+			sum += uintptr(i)
+		}
+	}
+}
+
+func BenchmarkSimpleIterUintptr_1000(b *testing.B) {
+	var iMax uintptr = 1000
+	var sum uintptr = 0
+	for i := 0; i < b.N; i++ {
+		var i uintptr = 0
+		for i < iMax {
+			i++
+			sum += i
+		}
+	}
+}
+
+func BenchmarkMultiplyUInt32_1000(b *testing.B) {
+	var iMax uint32 = 1000
+	var sz uint32 = 4
+	var sum uintptr = 0
+	for i := 0; i < b.N; i++ {
+		var i uint32 = 0
+		for i < iMax {
+			i++
+			sum += uintptr(i * sz)
+		}
+	}
+}
+
+func BenchmarkMultiplyUIntptr_1000(b *testing.B) {
+	var iMax uintptr = 1000
+	var sz uintptr = 4
+	var sum uintptr = 0
+	for i := 0; i < b.N; i++ {
+		var i uintptr = 0
+		for i < iMax {
+			i++
+			sum += i * sz
+		}
+	}
+}