Repository: mlange-42/arche
Pull request #76 (2023-02-14 16:23:23)

Entity initialization with components


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a4b948..9e77463 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
 * Logic filters for complex queries (#54, #58, #61)
 * `Query` and `World` have a method `Mask(Entity)` to access archetype masks (#65)
 * 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)
 
 ### Other
 
diff --git a/ecs/archetype.go b/ecs/archetype.go
index ae32fdf..aedff73 100644
--- a/ecs/archetype.go
+++ b/ecs/archetype.go
@@ -69,6 +69,16 @@ func (a *archetype) GetUnsafe(index uint32, id ID) unsafe.Pointer {
 	return a.components[a.indices[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)
+	len := len(a.components)
+	for i := 0; i < len; i++ {
+		a.components[i].Alloc()
+	}
+	return idx
+}
+
 // Add adds an entity with components to the archetype
 func (a *archetype) Add(entity Entity, components ...Component) uint32 {
 	if len(components) != len(a.Ids) {
diff --git a/ecs/storage.go b/ecs/storage.go
index 5b615ad..69ddc82 100644
--- a/ecs/storage.go
+++ b/ecs/storage.go
@@ -22,6 +22,9 @@ func (s *storage) Init(tp reflect.Type, increment int) {
 	size := tp.Size()
 	align := uintptr(tp.Align())
 	size = (size + (align - 1)) / align * align
+	if size == 0 {
+		size = align
+	}
 
 	s.buffer = reflect.New(reflect.ArrayOf(1, tp)).Elem()
 	s.bufferAddress = s.buffer.Addr().UnsafePointer()
diff --git a/ecs/world.go b/ecs/world.go
index bfc1850..286a07d 100644
--- a/ecs/world.go
+++ b/ecs/world.go
@@ -50,14 +50,57 @@ func FromConfig(conf Config) World {
 }
 
 // NewEntity returns a new or recycled [Entity].
+// The given component types are added to the entity.
 //
 // Panics when called on a locked world.
 // Do not use during [Query] iteration!
-func (w *World) NewEntity() Entity {
+func (w *World) NewEntity(comps ...ID) Entity {
 	w.checkLocked()
 
 	entity := w.entityPool.Get()
-	idx := w.archetypes.Get(0).Add(entity)
+
+	arch := w.archetypes.Get(0)
+	if len(comps) > 0 {
+		arch = w.findOrCreateArchetype(arch, comps, nil)
+	}
+
+	idx := arch.Alloc(entity)
+	len := len(w.entities)
+	if int(entity.id) == len {
+		if len == cap(w.entities) {
+			old := w.entities
+			w.entities = make([]entityIndex, len, len+w.config.CapacityIncrement)
+			copy(w.entities, old)
+		}
+		w.entities = append(w.entities, entityIndex{arch: arch, index: idx})
+	} else {
+		w.entities[entity.id] = entityIndex{arch: arch, index: idx}
+	}
+	return entity
+}
+
+// NewEntityWith returns a new or recycled [Entity].
+// The given component values are assigned to the entity.
+//
+// Panics when called on a locked world.
+// Do not use during [Query] iteration!
+func (w *World) NewEntityWith(comps ...Component) Entity {
+	w.checkLocked()
+
+	if len(comps) == 0 {
+		return w.NewEntity()
+	}
+
+	ids := make([]ID, len(comps))
+	for i, c := range comps {
+		ids[i] = c.ID
+	}
+
+	entity := w.entityPool.Get()
+	arch := w.archetypes.Get(0)
+	arch = w.findOrCreateArchetype(arch, ids, nil)
+
+	idx := arch.Alloc(entity)
 	len := len(w.entities)
 	if int(entity.id) == len {
 		if len == cap(w.entities) {
@@ -65,10 +108,15 @@ func (w *World) NewEntity() Entity {
 			w.entities = make([]entityIndex, len, len+w.config.CapacityIncrement)
 			copy(w.entities, old)
 		}
-		w.entities = append(w.entities, entityIndex{arch: w.archetypes.Get(0), index: idx})
+		w.entities = append(w.entities, entityIndex{arch: arch, index: idx})
 	} else {
-		w.entities[entity.id] = entityIndex{arch: w.archetypes.Get(0), index: idx}
+		w.entities[entity.id] = entityIndex{arch: arch, index: idx}
 	}
+
+	for _, c := range comps {
+		w.copyTo(entity, c.ID, c.Component)
+	}
+
 	return entity
 }
 
@@ -126,6 +174,7 @@ func (w *World) Alive(entity Entity) bool {
 // See also [github.com/mlange-42/arche/generic.Map.Get] for a generic variant.
 func (w *World) Get(entity Entity, comp ID) unsafe.Pointer {
 	index := w.entities[entity.id]
+
 	return index.arch.Get(index.index, comp)
 }
 
@@ -338,6 +387,7 @@ func (w *World) copyTo(entity Entity, id ID, comp interface{}) unsafe.Pointer {
 	}
 	index := w.entities[entity.id]
 	arch := index.arch
+
 	return arch.Set(index.index, id, comp)
 }
 
diff --git a/generic/functions.go b/generic/functions.go
index 3905148..164dffc 100644
--- a/generic/functions.go
+++ b/generic/functions.go
@@ -4,7 +4,127 @@ import (
 	"github.com/mlange-42/arche/ecs"
 )
 
-// Add1 adds a component type to an entity.
+// NewEntity1 creates a new entity with two component types.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntity1[A any](w *ecs.World) (ecs.Entity, *A) {
+	idA := ecs.ComponentID[A](w)
+	entity := w.NewEntity(idA)
+	return entity, (*A)(w.Get(entity, idA))
+}
+
+// NewEntity2 creates a new entity with two component types.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntity2[A any, B any](w *ecs.World) (ecs.Entity, *A, *B) {
+	idA := ecs.ComponentID[A](w)
+	idB := ecs.ComponentID[B](w)
+	entity := w.NewEntity(idA, idB)
+	return entity, (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB))
+}
+
+// NewEntity3 creates a new entity with three component types.
+// Returns the entity and the new components.
+//
+// See also and [ecs.World.NewEntity].
+func NewEntity3[A any, B any, C any](w *ecs.World) (ecs.Entity, *A, *B, *C) {
+	idA := ecs.ComponentID[A](w)
+	idB := ecs.ComponentID[B](w)
+	idC := ecs.ComponentID[C](w)
+	entity := w.NewEntity(idA, idB, idC)
+	return entity, (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC))
+}
+
+// NewEntity4 creates a new entity with four component types.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntity4[A any, B any, C any, D any](w *ecs.World) (ecs.Entity, *A, *B, *C, *D) {
+	idA := ecs.ComponentID[A](w)
+	idB := ecs.ComponentID[B](w)
+	idC := ecs.ComponentID[C](w)
+	idD := ecs.ComponentID[D](w)
+	entity := w.NewEntity(idA, idB, idC, idD)
+	return entity, (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC)), (*D)(w.Get(entity, idD))
+}
+
+// NewEntity5 creates a new entity with five component types.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntity5[A any, B any, C any, D any, E any](w *ecs.World) (ecs.Entity, *A, *B, *C, *D, *E) {
+	idA := ecs.ComponentID[A](w)
+	idB := ecs.ComponentID[B](w)
+	idC := ecs.ComponentID[C](w)
+	idD := ecs.ComponentID[D](w)
+	idE := ecs.ComponentID[E](w)
+	entity := w.NewEntity(idA, idB, idC, idD, idE)
+	return entity, (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC)), (*D)(w.Get(entity, idD)), (*E)(w.Get(entity, idE))
+}
+
+// NewEntityWith1 creates a new entity with one component, taken from a pointer.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntityWith1[A any](w *ecs.World, a *A) (ecs.Entity, *A) {
+	idA := ecs.ComponentID[A](w)
+	entity := w.NewEntityWith(ecs.Component{ID: idA, Component: a})
+	return entity, (*A)(w.Get(entity, idA))
+}
+
+// NewEntityWith2 creates a new entity with two components, taken from pointers.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntityWith2[A any, B any](w *ecs.World, a *A, b *B) (ecs.Entity, *A, *B) {
+	idA := ecs.ComponentID[A](w)
+	idB := ecs.ComponentID[B](w)
+	entity := w.NewEntityWith(ecs.Component{ID: idA, Component: a}, ecs.Component{ID: idB, Component: b})
+	return entity, (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB))
+}
+
+// NewEntityWith3 creates a new entity with three components, taken from pointers.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntityWith3[A any, B any, C any](w *ecs.World, a *A, b *B, c *C) (ecs.Entity, *A, *B, *C) {
+	idA := ecs.ComponentID[A](w)
+	idB := ecs.ComponentID[B](w)
+	idC := ecs.ComponentID[C](w)
+	entity := w.NewEntityWith(ecs.Component{ID: idA, Component: a}, ecs.Component{ID: idB, Component: b}, ecs.Component{ID: idC, Component: c})
+	return entity, (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC))
+}
+
+// NewEntityWith4 creates a new entity with four components, taken from pointers.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntityWith4[A any, B any, C any, D any](w *ecs.World, a *A, b *B, c *C, d *D) (ecs.Entity, *A, *B, *C, *D) {
+	idA := ecs.ComponentID[A](w)
+	idB := ecs.ComponentID[B](w)
+	idC := ecs.ComponentID[C](w)
+	idD := ecs.ComponentID[D](w)
+	entity := w.NewEntityWith(ecs.Component{ID: idA, Component: a}, ecs.Component{ID: idB, Component: b}, ecs.Component{ID: idC, Component: c}, ecs.Component{ID: idD, Component: d})
+	return entity, (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC)), (*D)(w.Get(entity, idD))
+}
+
+// NewEntityWith5 creates a new entity with five components, taken from pointers.
+// Returns the entity and the new components.
+//
+// See also [ecs.World.NewEntity].
+func NewEntityWith5[A any, B any, C any, D any, E any](w *ecs.World, a *A, b *B, c *C, d *D, e *E) (ecs.Entity, *A, *B, *C, *D, *E) {
+	idA := ecs.ComponentID[A](w)
+	idB := ecs.ComponentID[B](w)
+	idC := ecs.ComponentID[C](w)
+	idD := ecs.ComponentID[D](w)
+	idE := ecs.ComponentID[E](w)
+	entity := w.NewEntityWith(ecs.Component{ID: idA, Component: a}, ecs.Component{ID: idB, Component: b}, ecs.Component{ID: idC, Component: c}, ecs.Component{ID: idD, Component: d}, ecs.Component{ID: idE, Component: e})
+	return entity, (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC)), (*D)(w.Get(entity, idD)), (*E)(w.Get(entity, idE))
+}
+
+// Add1 adds a component type to an entity, and returns the component.
 //
 // See also [ecs.World.Add].
 func Add1[A any](w *ecs.World, entity ecs.Entity) *A {
@@ -13,7 +133,7 @@ func Add1[A any](w *ecs.World, entity ecs.Entity) *A {
 	return (*A)(w.Get(entity, id))
 }
 
-// Add2 adds two component types to an entity.
+// Add2 adds two component types to an entity, and returns the components.
 //
 // See also [ecs.World.Add].
 func Add2[A any, B any](w *ecs.World, entity ecs.Entity) (*A, *B) {
@@ -23,7 +143,7 @@ func Add2[A any, B any](w *ecs.World, entity ecs.Entity) (*A, *B) {
 	return (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB))
 }
 
-// Add3 adds three component types to an entity.
+// Add3 adds three component types to an entity, and returns the components.
 //
 // See also [ecs.World.Add].
 func Add3[A any, B any, C any](w *ecs.World, entity ecs.Entity) (*A, *B, *C) {
@@ -34,7 +154,7 @@ func Add3[A any, B any, C any](w *ecs.World, entity ecs.Entity) (*A, *B, *C) {
 	return (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC))
 }
 
-// Add4 adds four component types to an entity.
+// Add4 adds four component types to an entity, and returns the components.
 //
 // See also [ecs.World.Add].
 func Add4[A any, B any, C any, D any](w *ecs.World, entity ecs.Entity) (*A, *B, *C, *D) {
@@ -46,7 +166,7 @@ func Add4[A any, B any, C any, D any](w *ecs.World, entity ecs.Entity) (*A, *B,
 	return (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC)), (*D)(w.Get(entity, idD))
 }
 
-// Add5 adds five component types to an entity.
+// Add5 adds five component types to an entity, and returns the components.
 //
 // See also [ecs.World.Add].
 func Add5[A any, B any, C any, D any, E any](w *ecs.World, entity ecs.Entity) (*A, *B, *C, *D, *E) {
@@ -101,7 +221,7 @@ func Assign4[A any, B any, C any, D any](w *ecs.World, entity ecs.Entity, a *A,
 	return (*A)(w.Get(entity, idA)), (*B)(w.Get(entity, idB)), (*C)(w.Get(entity, idC)), (*D)(w.Get(entity, idD))
 }
 
-// Assign5 adds four components to an entity.
+// Assign5 adds five components to an entity.
 //
 // See also [ecs.World.Assign] and [ecs.World.AssignN].
 func Assign5[A any, B any, C any, D any, E any](w *ecs.World, entity ecs.Entity, a *A, b *B, c *C, d *D, e *E) (*A, *B, *C, *D, *E) {
diff --git a/generic/map.go b/generic/map.go
index 982b174..e3d5c90 100644
--- a/generic/map.go
+++ b/generic/map.go
@@ -22,6 +22,11 @@ func NewMap[T any](w *ecs.World) Map[T] {
 	}
 }
 
+// ID returns the component ID for this Map.
+func (g *Map[T]) ID() ecs.ID {
+	return g.id
+}
+
 // Get gets the component for the given entity.
 //
 // See also [ecs.World.Get].


Test patch:
diff --git a/ecs/world_test.go b/ecs/world_test.go
index 4b2346c..1818a58 100644
--- a/ecs/world_test.go
+++ b/ecs/world_test.go
@@ -40,6 +40,57 @@ func TestWorldEntites(t *testing.T) {
 	assert.Panics(t, func() { w.RemEntity(newEntityGen(1, 0)) })
 }
 
+func TestWorldNewEntites(t *testing.T) {
+	w := NewConfig().WithCapacityIncrement(32).Build()
+
+	posID := ComponentID[position](&w)
+	velID := ComponentID[velocity](&w)
+	rotID := ComponentID[rotation](&w)
+
+	e0 := w.NewEntity()
+	e1 := w.NewEntity(posID, velID, rotID)
+	e2 := w.NewEntityWith(
+		Component{posID, &position{1, 2}},
+		Component{velID, &velocity{3, 4}},
+		Component{rotID, &rotation{5}},
+	)
+	e3 := w.NewEntityWith()
+
+	assert.Equal(t, NewBitMask(), w.Mask(e0))
+	assert.Equal(t, NewBitMask(posID, velID, rotID), w.Mask(e1))
+	assert.Equal(t, NewBitMask(posID, velID, rotID), w.Mask(e2))
+	assert.Equal(t, NewBitMask(), w.Mask(e3))
+
+	pos := (*position)(w.Get(e2, posID))
+	vel := (*velocity)(w.Get(e2, velID))
+	rot := (*rotation)(w.Get(e2, rotID))
+
+	assert.Equal(t, &position{1, 2}, pos)
+	assert.Equal(t, &velocity{3, 4}, vel)
+	assert.Equal(t, &rotation{5}, rot)
+
+	w.RemEntity(e0)
+	w.RemEntity(e1)
+	w.RemEntity(e2)
+	w.RemEntity(e3)
+
+	for i := 0; i < 35; i++ {
+		e := w.NewEntityWith(
+			Component{posID, &position{i + 1, i + 2}},
+			Component{velID, &velocity{i + 3, i + 4}},
+			Component{rotID, &rotation{i + 5}},
+		)
+
+		pos := (*position)(w.Get(e, posID))
+		vel := (*velocity)(w.Get(e, velID))
+		rot := (*rotation)(w.Get(e, rotID))
+
+		assert.Equal(t, &position{i + 1, i + 2}, pos)
+		assert.Equal(t, &velocity{i + 3, i + 4}, vel)
+		assert.Equal(t, &rotation{i + 5}, rot)
+	}
+}
+
 func TestWorldComponents(t *testing.T) {
 	w := NewWorld()
 
@@ -200,16 +251,20 @@ func TestWorldAssignSet(t *testing.T) {
 
 	e2 := w.NewEntity()
 	w.AssignN(e2,
+		Component{posID, &position{4, 5}},
 		Component{velID, &velocity{1, 2}},
 		Component{rotID, &rotation{3}},
-		Component{posID, &position{4, 5}},
 	)
 	assert.True(t, w.Has(e2, velID))
 	assert.True(t, w.Has(e2, rotID))
 	assert.True(t, w.Has(e2, posID))
 
 	pos = (*position)(w.Get(e2, posID))
-	assert.Equal(t, 4, pos.X)
+	rot := (*rotation)(w.Get(e2, rotID))
+	vel := (*velocity)(w.Get(e2, velID))
+	assert.Equal(t, &position{4, 5}, pos)
+	assert.Equal(t, &rotation{3}, rot)
+	assert.Equal(t, &velocity{1, 2}, vel)
 
 	_ = (*position)(w.Set(e2, posID, &position{7, 8}))
 	pos = (*position)(w.Get(e2, posID))
diff --git a/generic/functions_test.go b/generic/functions_test.go
index 471dc87..c61dd78 100644
--- a/generic/functions_test.go
+++ b/generic/functions_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/mlange-42/arche/ecs"
+	"github.com/stretchr/testify/assert"
 )
 
 //lint:ignore U1000 test type
@@ -159,3 +160,98 @@ func TestGenericAssignRemove(t *testing.T) {
 	Assign5(&w, e0, &testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{}, &testStruct4{})
 	Remove5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4](&w, e0)
 }
+
+func TestGenericNewEntity(t *testing.T) {
+	w := ecs.NewConfig().WithCapacityIncrement(32).Build()
+
+	id0 := ecs.ComponentID[testStruct0](&w)
+	id1 := ecs.ComponentID[testStruct1](&w)
+	id2 := ecs.ComponentID[testStruct2](&w)
+
+	e0 := w.NewEntity()
+	e1, _, _, _ := NewEntity3[testStruct0, testStruct1, testStruct2](&w)
+	e2, _, _, _ := NewEntityWith3(&w, &testStruct0{1}, &testStruct1{2}, &testStruct2{3, 4})
+	e3 := w.NewEntityWith()
+
+	assert.Equal(t, ecs.NewBitMask(), w.Mask(e0))
+	assert.Equal(t, ecs.NewBitMask(id0, id1, id2), w.Mask(e1))
+	assert.Equal(t, ecs.NewBitMask(id0, id1, id2), w.Mask(e2))
+	assert.Equal(t, ecs.NewBitMask(), w.Mask(e3))
+
+	s0 := (*testStruct0)(w.Get(e2, id0))
+	s1 := (*testStruct1)(w.Get(e2, id1))
+	s2 := (*testStruct2)(w.Get(e2, id2))
+
+	assert.Equal(t, &testStruct0{1}, s0)
+	assert.Equal(t, &testStruct1{2}, s1)
+	assert.Equal(t, &testStruct2{3, 4}, s2)
+
+	w.RemEntity(e0)
+	w.RemEntity(e1)
+	w.RemEntity(e2)
+	w.RemEntity(e3)
+
+	for i := 0; i < 35; i++ {
+		e, _, _, _ := NewEntityWith3(&w,
+			&testStruct0{int8(i + 1)},
+			&testStruct1{int32(i + 2)},
+			&testStruct2{int32(i + 3), int32(i + 4)},
+		)
+
+		s0 := (*testStruct0)(w.Get(e, id0))
+		s1 := (*testStruct1)(w.Get(e, id1))
+		s2 := (*testStruct2)(w.Get(e, id2))
+
+		assert.Equal(t, &testStruct0{int8(i + 1)}, s0)
+		assert.Equal(t, &testStruct1{int32(i + 2)}, s1)
+		assert.Equal(t, &testStruct2{int32(i + 3), int32(i + 4)}, s2)
+	}
+}
+
+func TestGenericNewEntityAll(t *testing.T) {
+	w := ecs.NewWorld()
+
+	e, _ := NewEntity1[testStruct0](&w)
+	Remove1[testStruct0](&w, e)
+	w.Remove(e)
+
+	e, _, _ = NewEntity2[testStruct0, testStruct1](&w)
+	Remove2[testStruct0, testStruct1](&w, e)
+	w.Remove(e)
+
+	e, _, _, _ = NewEntity3[testStruct0, testStruct1, testStruct2](&w)
+	Remove3[testStruct0, testStruct1, testStruct2](&w, e)
+	w.Remove(e)
+
+	e, _, _, _, _ = NewEntity4[testStruct0, testStruct1, testStruct2, testStruct3](&w)
+	Remove4[testStruct0, testStruct1, testStruct2, testStruct3](&w, e)
+	w.Remove(e)
+
+	e, _, _, _, _, _ = NewEntity5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4](&w)
+	Remove5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4](&w, e)
+	w.Remove(e)
+}
+
+func TestGenericNewEntityWithAll(t *testing.T) {
+	w := ecs.NewWorld()
+
+	e, _ := NewEntityWith1(&w, &testStruct0{})
+	Remove1[testStruct0](&w, e)
+	w.Remove(e)
+
+	e, _, _ = NewEntityWith2(&w, &testStruct0{}, &testStruct1{})
+	Remove2[testStruct0, testStruct1](&w, e)
+	w.Remove(e)
+
+	e, _, _, _ = NewEntityWith3(&w, &testStruct0{}, &testStruct1{}, &testStruct2{})
+	Remove3[testStruct0, testStruct1, testStruct2](&w, e)
+	w.Remove(e)
+
+	e, _, _, _, _ = NewEntityWith4(&w, &testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{})
+	Remove4[testStruct0, testStruct1, testStruct2, testStruct3](&w, e)
+	w.Remove(e)
+
+	e, _, _, _, _, _ = NewEntityWith5(&w, &testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{}, &testStruct4{})
+	Remove5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4](&w, e)
+	w.Remove(e)
+}
diff --git a/generic/map_test.go b/generic/map_test.go
index a8ce356..3df0e61 100644
--- a/generic/map_test.go
+++ b/generic/map_test.go
@@ -11,6 +11,8 @@ func TestGenericMap(t *testing.T) {
 	w := ecs.NewWorld()
 	get := NewMap[testStruct0](&w)
 
+	assert.Equal(t, ecs.ComponentID[testStruct0](&w), get.ID())
+
 	e0 := w.NewEntity()
 
 	Add1[testStruct0](&w, e0)
@@ -24,5 +26,6 @@ func TestGenericMap(t *testing.T) {
 	assert.Equal(t, 100, int(str.val))
 
 	get2 := NewMap[testStruct1](&w)
+	assert.Equal(t, ecs.ComponentID[testStruct1](&w), get2.ID())
 	assert.Panics(t, func() { get2.Set(e0, &testStruct1{}) })
 }