Repository: mlange-42/arche
Pull request #8 (2023-02-06 09:56:10)

Component registry, add/remove entities


Patch:
diff --git a/ecs/archetype.go b/ecs/archetype.go
index f98c66a..648bda2 100644
--- a/ecs/archetype.go
+++ b/ecs/archetype.go
@@ -1,6 +1,7 @@
 package ecs
 
 import (
+	"reflect"
 	"unsafe"
 )
 
@@ -12,22 +13,31 @@ type Archetype struct {
 	components []Storage
 }
 
+var entityType = reflect.TypeOf(Entity{})
+
 // NewArchetype creates a new Archetype for the given components
 // Component arguments must be sorted by ID!
-func NewArchetype(components ...Component) Archetype {
+func NewArchetype(components ...ComponentType) Archetype {
 	var mask Mask
 	indices := make([]ID, len(components))
 	comps := make([]Storage, MaskTotalBits)
+
+	prev := -1
 	for i, c := range components {
+		if int(c.ID) <= prev {
+			panic("component arguments must be sorted by ID")
+		}
+		prev = int(c.ID)
+
 		mask.Set(c.ID, true)
 		indices[i] = c.ID
-		comps[c.ID] = NewReflectStorage(c.Component, 32)
+		comps[c.ID] = NewReflectStorage(c.Type, 32)
 	}
 
 	return Archetype{
 		mask:       mask,
 		indices:    indices,
-		entities:   NewReflectStorage(Entity{}, 32),
+		entities:   NewReflectStorage(entityType, 32),
 		components: comps,
 	}
 }
@@ -37,26 +47,59 @@ func (a *Archetype) GetEntity(index int) Entity {
 	return *(*Entity)(a.entities.Get(uint32(index)))
 }
 
-// GetEntity returns the component with the given ID at the given index
+// Get returns the component with the given ID at the given index
 func (a *Archetype) Get(index int, id ID) unsafe.Pointer {
 	return a.components[id].Get(uint32(index))
 }
 
 // Add adds an entity with components to the archetype
-func (a *Archetype) Add(entity Entity, components ...Component) {
+func (a *Archetype) Add(entity Entity, components ...Component) uint32 {
 	if len(components) != len(a.indices) {
 		panic("Invalid number of components")
 	}
-	a.entities.Add(&entity)
+	idx := a.entities.Add(&entity)
 	for _, c := range components {
 		a.components[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.indices) {
+		panic("Invalid number of components")
+	}
+	idx := a.entities.Add(&entity)
+	for _, c := range components {
+		if c.Pointer == nil {
+			a.components[c.ID].Alloc()
+		} else {
+			a.components[c.ID].AddPointer(c.Pointer)
+		}
+	}
+	return idx
 }
 
 // Remove removes an entity from the archetype
-func (a *Archetype) Remove(index int) {
-	a.entities.Remove(uint32(index))
+func (a *Archetype) Remove(index int) bool {
+	swapped := a.entities.Remove(uint32(index))
 	for _, c := range a.indices {
 		a.components[c].Remove(uint32(index))
 	}
+	return swapped
+}
+
+// Components returns the component IDs for this archetype
+func (a *Archetype) Components() []ID {
+	return a.indices
+}
+
+// HasComponent returns whether the archetype contains the given component ID
+func (a *Archetype) HasComponent(id ID) bool {
+	return a.mask.Get(id)
+}
+
+// Len reports the number of entities in the archetype
+func (a *Archetype) Len() uint32 {
+	return a.entities.Len()
 }
diff --git a/ecs/entity.go b/ecs/entity.go
index 1352395..f5452d3 100644
--- a/ecs/entity.go
+++ b/ecs/entity.go
@@ -10,3 +10,8 @@ type Entity struct {
 func newEntity(id int) Entity {
 	return Entity{ID(id), 0}
 }
+
+type entityIndex struct {
+	arch  int
+	index uint32
+}
diff --git a/ecs/registry.go b/ecs/registry.go
new file mode 100644
index 0000000..b030ace
--- /dev/null
+++ b/ecs/registry.go
@@ -0,0 +1,38 @@
+package ecs
+
+import "reflect"
+
+// ComponentRegistry keeps track of component IDs
+type ComponentRegistry struct {
+	components map[reflect.Type]ID
+	types      []reflect.Type
+}
+
+// NewComponentRegistry creates a new ComponentRegistry
+func NewComponentRegistry() ComponentRegistry {
+	return ComponentRegistry{
+		components: map[reflect.Type]ID{},
+		types:      make([]reflect.Type, MaskTotalBits),
+	}
+}
+
+// RegisterComponent registers a components and assigns an ID for it
+func (r *ComponentRegistry) RegisterComponent(tp reflect.Type) ID {
+	id := ID(len(r.components))
+	r.components[tp] = id
+	r.types[id] = tp
+	return id
+}
+
+// ComponentID returns the ID for a component type, and registers it if not already registered
+func (r *ComponentRegistry) ComponentID(tp reflect.Type) ID {
+	if id, ok := r.components[tp]; ok {
+		return id
+	}
+	return r.RegisterComponent(tp)
+}
+
+// ComponentType returns the type of a component by ID
+func (r *ComponentRegistry) ComponentType(id ID) reflect.Type {
+	return r.types[id]
+}
diff --git a/ecs/storage.go b/ecs/storage.go
index 8f10ce7..785b350 100644
--- a/ecs/storage.go
+++ b/ecs/storage.go
@@ -18,8 +18,7 @@ type Storage struct {
 }
 
 // NewReflectStorage creates a new ReflectStorage
-func NewReflectStorage(obj interface{}, increment int) Storage {
-	tp := reflect.TypeOf(obj)
+func NewReflectStorage(tp reflect.Type, increment int) Storage {
 	size := tp.Size()
 
 	buffer := reflect.New(reflect.ArrayOf(increment, tp)).Elem()
@@ -34,6 +33,12 @@ func NewReflectStorage(obj interface{}, increment int) Storage {
 	}
 }
 
+// NewReflectStorageFromTemplate creates a new ReflectStorage from a template object
+func NewReflectStorageFromTemplate(obj interface{}, increment int) Storage {
+	tp := reflect.TypeOf(obj)
+	return NewReflectStorage(tp, 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)
@@ -42,6 +47,29 @@ func (s *Storage) Get(index uint32) unsafe.Pointer {
 
 // Add adds an element to the end of the storage
 func (s *Storage) Add(value interface{}) (index uint32) {
+	s.extend()
+	s.len++
+	s.set(s.len-1, value)
+	return s.len - 1
+}
+
+// AddPointer adds an element to the end of the storage, based on a pointer
+func (s *Storage) AddPointer(value unsafe.Pointer) (index uint32) {
+	s.extend()
+	s.len++
+	s.setPointer(s.len-1, value)
+	return s.len - 1
+}
+
+// Alloc adds an empty element to the end of the storage
+func (s *Storage) Alloc() (index uint32) {
+	s.extend()
+	s.len++
+	s.Zero(s.len - 1)
+	return s.len - 1
+}
+
+func (s *Storage) extend() {
 	if s.cap < s.len+1 {
 		old := s.buffer
 		s.cap = s.cap + s.capacityIncrement
@@ -49,27 +77,31 @@ func (s *Storage) Add(value interface{}) (index uint32) {
 		s.bufferAddress = s.buffer.Addr().UnsafePointer()
 		reflect.Copy(s.buffer, old)
 	}
-	s.len++
-	s.set(s.len-1, value)
-	return s.len - 1
 }
 
 // Remove swap-removes an element
-func (s *Storage) Remove(index uint32) {
+func (s *Storage) Remove(index uint32) bool {
 	o := s.len - 1
 	n := index
-	size := s.itemSize
 
-	src := unsafe.Add(s.bufferAddress, uintptr(o)*s.itemSize)
-	dst := unsafe.Add(s.bufferAddress, uintptr(n)*s.itemSize)
+	// TODO shrink the underlying data arrays
+	if n < o {
+		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]
+
+		copy(dstSlice, srcSlice)
+
+		s.len--
+		return true
+	}
 
 	s.len--
-	// TODO shrink the underlying data arrays
+	return false
 }
 
 func (s *Storage) set(index uint32, value interface{}) {
@@ -86,6 +118,26 @@ func (s *Storage) set(index uint32, value interface{}) {
 	copy(dstSlice, srcSlice)
 }
 
+func (s *Storage) setPointer(index uint32, value unsafe.Pointer) {
+	dst := s.Get(index)
+	size := s.itemSize
+
+	dstSlice := (*[math.MaxInt32]byte)(dst)[:size:size]
+	srcSlice := (*[math.MaxInt32]byte)(value)[:size:size]
+
+	copy(dstSlice, srcSlice)
+}
+
+// Zero resets a block of storage
+func (s *Storage) Zero(index uint32) {
+	dst := s.Get(index)
+
+	for i := uintptr(0); i < s.itemSize; i++ {
+		*(*byte)(dst) = 0
+		dst = unsafe.Add(dst, 1)
+	}
+}
+
 // Len returns the number of items in the storage
 func (s *Storage) Len() uint32 {
 	return s.len
diff --git a/ecs/types.go b/ecs/types.go
index a5334f3..7b59623 100644
--- a/ecs/types.go
+++ b/ecs/types.go
@@ -1,5 +1,10 @@
 package ecs
 
+import (
+	"reflect"
+	"unsafe"
+)
+
 // ID defines the format for the components identifier
 type ID uint32
 
@@ -8,3 +13,15 @@ type Component struct {
 	ID
 	Component interface{}
 }
+
+// ComponentType is a component ID with a data type
+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 47091cc..ec12f96 100644
--- a/ecs/world.go
+++ b/ecs/world.go
@@ -1,26 +1,210 @@
 package ecs
 
+import (
+	"reflect"
+	"sort"
+	"unsafe"
+)
+
 // World is the interface for the ECS world
 type World interface {
 	NewEntity() Entity
-	RemEntity(entity Entity)
+	RemEntity(entity Entity) bool
+	Get(entity Entity, comps ID) unsafe.Pointer
+	Has(entity Entity, comps ID) bool
+	Add(entity Entity, comps ...ID)
+	Remove(entity Entity, comps ...ID)
+	Alive(entity Entity) bool
+	Registry() *ComponentRegistry
 }
 
 // NewWorld creates a new World
 func NewWorld() World {
+	return newWorld()
+}
+
+func newWorld() *world {
 	return &world{
+		entities:   []entityIndex{},
 		entityPool: NewEntityPool(),
+		registry:   NewComponentRegistry(),
+		archetypes: []Archetype{NewArchetype()},
 	}
 }
 
 type world struct {
+	entities   []entityIndex
+	archetypes []Archetype
 	entityPool EntityPool
+	registry   ComponentRegistry
 }
 
 func (w *world) NewEntity() Entity {
-	return w.entityPool.Get()
+	entity := w.entityPool.Get()
+	idx := w.archetypes[0].Add(entity)
+	if int(entity.id) == len(w.entities) {
+		w.entities = append(w.entities, entityIndex{0, idx})
+	} else {
+		w.entities[entity.id] = entityIndex{0, idx}
+	}
+	return entity
 }
 
-func (w *world) RemEntity(entity Entity) {
+func (w *world) RemEntity(entity Entity) bool {
+	if !w.entityPool.Alive(entity) {
+		return false
+	}
+
+	index := w.entities[entity.id]
+	oldArch := &w.archetypes[index.arch]
+	swapped := oldArch.Remove(int(index.index))
 	w.entityPool.Recycle(entity)
+
+	if swapped {
+		swapEntity := oldArch.GetEntity(int(index.index))
+		w.entities[swapEntity.id].index = index.index
+	}
+
+	w.entities[entity.id].arch = -1
+	return true
+}
+
+func (w *world) Get(entity Entity, comp ID) unsafe.Pointer {
+	index := w.entities[entity.id]
+	arch := w.archetypes[index.arch]
+
+	if !arch.HasComponent(comp) {
+		return nil
+	}
+
+	return arch.Get(int(index.index), comp)
+}
+
+func (w *world) Has(entity Entity, comp ID) bool {
+	index := w.entities[entity.id]
+	arch := w.archetypes[index.arch]
+	return arch.HasComponent(comp)
+}
+
+func (w *world) Add(entity Entity, comps ...ID) {
+	if len(comps) == 0 {
+		return
+	}
+	index := w.entities[entity.id]
+	oldArch := &w.archetypes[index.arch]
+	mask := oldArch.mask
+	for _, comp := range comps {
+		mask.Set(comp, true)
+	}
+
+	oldIDs := oldArch.Components()
+
+	archIdx, ok := w.findArchetype(mask)
+	if !ok {
+		ids := append(oldIDs, comps...)
+		archIdx = w.createArchetype(ids...)
+	}
+	arch := &w.archetypes[archIdx]
+	oldArch = &w.archetypes[index.arch]
+
+	allComps := make([]ComponentPointer, 0, len(oldIDs)+len(comps))
+	for _, id := range oldIDs {
+		comp := oldArch.Get(int(index.index), id)
+		allComps = append(allComps, ComponentPointer{id, comp})
+	}
+	for _, id := range comps {
+		allComps = append(allComps, ComponentPointer{id, nil})
+	}
+
+	newIndex := arch.AddPointer(entity, allComps...)
+
+	swapped := oldArch.Remove(int(index.index))
+
+	if swapped {
+		swapEntity := oldArch.GetEntity(int(index.index))
+		w.entities[swapEntity.id].index = index.index
+	}
+	w.entities[entity.id] = entityIndex{archIdx, newIndex}
+}
+
+func (w *world) Remove(entity Entity, comps ...ID) {
+	if len(comps) == 0 {
+		return
+	}
+	index := w.entities[entity.id]
+	oldArch := &w.archetypes[index.arch]
+	mask := oldArch.mask
+	for _, comp := range comps {
+		mask.Set(comp, false)
+	}
+
+	oldIDs := oldArch.Components()
+	newIDs := make([]ID, 0, len(oldIDs))
+	for _, id := range oldIDs {
+		if mask.Get(id) {
+			newIDs = append(newIDs, id)
+		}
+	}
+
+	archIdx, ok := w.findArchetype(mask)
+	if !ok {
+		archIdx = w.createArchetype(newIDs...)
+	}
+	arch := &w.archetypes[archIdx]
+	oldArch = &w.archetypes[index.arch]
+
+	allComps := make([]ComponentPointer, 0, len(newIDs))
+	for _, id := range newIDs {
+		comp := oldArch.Get(int(index.index), id)
+		allComps = append(allComps, ComponentPointer{id, comp})
+	}
+
+	newIndex := arch.AddPointer(entity, allComps...)
+
+	swapped := oldArch.Remove(int(index.index))
+	if swapped {
+		swapEntity := oldArch.GetEntity(int(index.index))
+		w.entities[swapEntity.id].index = index.index
+	}
+	w.entities[entity.id] = entityIndex{archIdx, newIndex}
+}
+
+func (w *world) findArchetype(mask Mask) (int, bool) {
+	for i, a := range w.archetypes {
+		if a.mask == mask {
+			return i, true
+		}
+	}
+	return 0, false
+}
+
+func (w *world) createArchetype(comps ...ID) int {
+	sort.Slice(comps, func(i, j int) bool { return comps[i] < comps[j] })
+	types := make([]ComponentType, len(comps))
+	for i, id := range comps {
+		types[i] = ComponentType{id, w.registry.types[id]}
+	}
+	a := NewArchetype(types...)
+	w.archetypes = append(w.archetypes, a)
+	return len(w.archetypes) - 1
+}
+
+func (w *world) Alive(entity Entity) bool {
+	return w.entityPool.Alive(entity)
+}
+
+func (w *world) Registry() *ComponentRegistry {
+	return &w.registry
+}
+
+// RegisterComponent provides a way to register components via generics
+func RegisterComponent[T any](w World) ID {
+	tp := reflect.TypeOf((*T)(nil)).Elem()
+	return w.Registry().RegisterComponent(tp)
+}
+
+// ComponentID provides a way to get a component's ID via generics
+func ComponentID[T any](w World) ID {
+	tp := reflect.TypeOf((*T)(nil)).Elem()
+	return w.Registry().ComponentID(tp)
 }


Test patch:
diff --git a/benchmark/arche_test.go b/benchmark/arche_test.go
index b3359dc..9d187d1 100644
--- a/benchmark/arche_test.go
+++ b/benchmark/arche_test.go
@@ -1,6 +1,7 @@
 package benchmark
 
 import (
+	"reflect"
 	"testing"
 
 	"github.com/mlange-42/arche/ecs"
@@ -9,9 +10,9 @@ import (
 func runArche(b *testing.B, count int) {
 	world := ecs.NewWorld()
 
-	comps := []ecs.Component{
-		{ID: 0, Component: position{}},
-		{ID: 1, Component: rotation{}},
+	comps := []ecs.ComponentType{
+		{ID: 0, Type: reflect.TypeOf(position{})},
+		{ID: 1, Type: reflect.TypeOf(rotation{})},
 	}
 
 	arch := ecs.NewArchetype(comps...)
diff --git a/ecs/archetype_test.go b/ecs/archetype_test.go
index b940101..b61b28e 100644
--- a/ecs/archetype_test.go
+++ b/ecs/archetype_test.go
@@ -1,38 +1,30 @@
 package ecs
 
 import (
+	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
-type Position struct {
-	X int
-	Y int
-}
-
-type Rotation struct {
-	Angle int
-}
-
 func TestArchetype(t *testing.T) {
-	comps := []Component{
-		{ID: 0, Component: Position{}},
-		{ID: 1, Component: Rotation{}},
+	comps := []ComponentType{
+		{ID: 0, Type: reflect.TypeOf(position{})},
+		{ID: 1, Type: reflect.TypeOf(rotation{})},
 	}
 
 	arch := NewArchetype(comps...)
 
 	arch.Add(
 		newEntity(0),
-		Component{ID: 0, Component: &Position{1, 2}},
-		Component{ID: 1, Component: &Rotation{3}},
+		Component{ID: 0, Component: &position{1, 2}},
+		Component{ID: 1, Component: &rotation{3}},
 	)
 
 	arch.Add(
 		newEntity(1),
-		Component{ID: 0, Component: &Position{4, 5}},
-		Component{ID: 1, Component: &Rotation{6}},
+		Component{ID: 0, Component: &position{4, 5}},
+		Component{ID: 1, Component: &rotation{6}},
 	)
 
 	assert.Equal(t, 2, int(arch.entities.Len()))
@@ -44,10 +36,10 @@ func TestArchetype(t *testing.T) {
 	assert.Equal(t, Entity{0, 0}, e0)
 	assert.Equal(t, Entity{1, 0}, e1)
 
-	pos0 := (*Position)(arch.Get(0, ID(0)))
-	rot0 := (*Rotation)(arch.Get(0, ID(1)))
-	pos1 := (*Position)(arch.Get(1, ID(0)))
-	rot1 := (*Rotation)(arch.Get(1, ID(1)))
+	pos0 := (*position)(arch.Get(0, ID(0)))
+	rot0 := (*rotation)(arch.Get(0, ID(1)))
+	pos1 := (*position)(arch.Get(1, ID(0)))
+	rot1 := (*rotation)(arch.Get(1, ID(1)))
 
 	assert.Equal(t, 1, pos0.X)
 	assert.Equal(t, 2, pos0.Y)
@@ -61,8 +53,8 @@ func TestArchetype(t *testing.T) {
 	assert.Equal(t, 1, int(arch.components[0].Len()))
 	assert.Equal(t, 1, int(arch.components[1].Len()))
 
-	pos0 = (*Position)(arch.Get(0, ID(0)))
-	rot0 = (*Rotation)(arch.Get(0, ID(1)))
+	pos0 = (*position)(arch.Get(0, ID(0)))
+	rot0 = (*rotation)(arch.Get(0, ID(1)))
 	assert.Equal(t, 4, pos0.X)
 	assert.Equal(t, 5, pos0.Y)
 	assert.Equal(t, 6, rot0.Angle)
@@ -70,24 +62,35 @@ func TestArchetype(t *testing.T) {
 	assert.Panics(t, func() {
 		arch.Add(
 			newEntity(1),
-			Component{ID: 0, Component: &Position{4, 5}},
+			Component{ID: 0, Component: &position{4, 5}},
 		)
 	})
+
+	assert.Panics(t, func() {
+		arch.AddPointer(newEntity(1))
+	})
 }
 
 func TestNewArchetype(t *testing.T) {
-	comps := []Component{
-		{ID: 0, Component: Position{}},
-		{ID: 1, Component: Rotation{}},
+	comps := []ComponentType{
+		{ID: 0, Type: reflect.TypeOf(position{})},
+		{ID: 1, Type: reflect.TypeOf(rotation{})},
 	}
-
 	_ = NewArchetype(comps...)
+
+	comps = []ComponentType{
+		{ID: 1, Type: reflect.TypeOf(rotation{})},
+		{ID: 0, Type: reflect.TypeOf(position{})},
+	}
+	assert.Panics(t, func() {
+		_ = NewArchetype(comps...)
+	})
 }
 
 func BenchmarkArchetypeAccess(b *testing.B) {
-	comps := []Component{
-		{ID: 0, Component: Position{}},
-		{ID: 1, Component: Rotation{}},
+	comps := []ComponentType{
+		{ID: 0, Type: reflect.TypeOf(position{})},
+		{ID: 1, Type: reflect.TypeOf(rotation{})},
 	}
 
 	arch := NewArchetype(comps...)
@@ -95,14 +98,14 @@ func BenchmarkArchetypeAccess(b *testing.B) {
 	for i := 0; i < 1000; i++ {
 		arch.Add(
 			newEntity(i),
-			Component{ID: 0, Component: &Position{1, 2}},
-			Component{ID: 1, Component: &Rotation{3}},
+			Component{ID: 0, Component: &position{1, 2}},
+			Component{ID: 1, Component: &rotation{3}},
 		)
 	}
 
 	for i := 0; i < b.N; i++ {
 		for j := 0; j < 1000; j++ {
-			pos := (*Position)(arch.Get(i, ID(0)))
+			pos := (*position)(arch.Get(i, ID(0)))
 			_ = pos
 		}
 	}
diff --git a/ecs/registry_test.go b/ecs/registry_test.go
new file mode 100644
index 0000000..c4d4011
--- /dev/null
+++ b/ecs/registry_test.go
@@ -0,0 +1,23 @@
+package ecs
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestComponentRegistry(t *testing.T) {
+	reg := NewComponentRegistry()
+
+	posType := reflect.TypeOf((*position)(nil)).Elem()
+	rotType := reflect.TypeOf((*rotation)(nil)).Elem()
+
+	reg.RegisterComponent(posType)
+
+	assert.Equal(t, ID(0), reg.ComponentID(posType))
+	assert.Equal(t, ID(1), reg.ComponentID(rotType))
+
+	assert.Equal(t, posType, reg.ComponentType(ID(0)))
+	assert.Equal(t, rotType, reg.ComponentType(ID(1)))
+}
diff --git a/ecs/storage_test.go b/ecs/storage_test.go
index c476bef..0531287 100644
--- a/ecs/storage_test.go
+++ b/ecs/storage_test.go
@@ -1,6 +1,7 @@
 package ecs
 
 import (
+	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -19,7 +20,7 @@ type simpleStruct struct {
 
 func TestReflectStorageAddGet(t *testing.T) {
 	obj1 := testStruct{}
-	s := NewReflectStorage(obj1, 1)
+	s := NewReflectStorageFromTemplate(obj1, 1)
 	storageAddGet(t, s)
 }
 
@@ -44,11 +45,14 @@ func storageAddGet(t *testing.T, s Storage) {
 	assert.Equal(t, testStruct{1, 1001, true, false}, *ret2, "Manipulating element does not change data")
 
 	assert.Equal(t, []testStruct{{}, {1, 1001, true, false}}, ToSlice[testStruct](s), "Wrong extracted struct slice")
+
+	s.Alloc()
+	assert.Equal(t, []testStruct{{}, {1, 1001, true, false}, {}}, ToSlice[testStruct](s), "Wrong extracted struct slice")
 }
 
 func TestReflectStorageRemove(t *testing.T) {
 	ref := simpleStruct{}
-	s := NewReflectStorage(ref, 32)
+	s := NewReflectStorageFromTemplate(ref, 32)
 
 	storageRemove(t, s)
 }
@@ -72,7 +76,7 @@ func storageRemove(t *testing.T, s Storage) {
 
 func TestReflectStorageDataSize(t *testing.T) {
 	ref := simpleStruct{}
-	s := NewReflectStorage(ref, 1)
+	s := NewReflectStorageFromTemplate(ref, 1)
 
 	for i := 0; i < 5; i++ {
 		obj := simpleStruct{i}
@@ -96,12 +100,12 @@ func TestReflectStorageDataSize(t *testing.T) {
 }
 
 func TestNewReflectStorage(t *testing.T) {
-	_ = NewReflectStorage(simpleStruct{}, 32)
+	_ = NewReflectStorage(reflect.TypeOf(simpleStruct{}), 32)
 }
 
 func BenchmarkIterReflectStorage(b *testing.B) {
 	ref := testStruct{}
-	s := NewReflectStorage(ref, 128)
+	s := NewReflectStorageFromTemplate(ref, 128)
 	for i := 0; i < 1000; i++ {
 		s.Add(&testStruct{})
 	}
@@ -149,7 +153,7 @@ func BenchmarkAddReflectStorage(b *testing.B) {
 	ref := testStruct{}
 	for i := 0; i < b.N; i++ {
 		b.StopTimer()
-		s := NewReflectStorage(ref, 1024)
+		s := NewReflectStorageFromTemplate(ref, 1024)
 		b.StartTimer()
 
 		for i := 0; i < 1000; i++ {
@@ -196,7 +200,7 @@ func BenchmarkRemoveReflectStorage(b *testing.B) {
 	ref := testStruct{}
 	for i := 0; i < b.N; i++ {
 		b.StopTimer()
-		s := NewReflectStorage(ref, 1024)
+		s := NewReflectStorageFromTemplate(ref, 1024)
 		for i := 0; i < 1000; i++ {
 			s.Add(&ref)
 		}
diff --git a/ecs/test_types.go b/ecs/test_types.go
new file mode 100644
index 0000000..39a327f
--- /dev/null
+++ b/ecs/test_types.go
@@ -0,0 +1,10 @@
+package ecs
+
+type position struct {
+	X int
+	Y int
+}
+
+type rotation struct {
+	Angle int
+}
diff --git a/ecs/world_test.go b/ecs/world_test.go
index 0e81e30..53e7b32 100644
--- a/ecs/world_test.go
+++ b/ecs/world_test.go
@@ -7,12 +7,134 @@ import (
 )
 
 func TestWorldEntites(t *testing.T) {
-	w := NewWorld()
+	w := newWorld()
 
 	assert.Equal(t, Entity{0, 0}, w.NewEntity())
 	assert.Equal(t, Entity{1, 0}, w.NewEntity())
 	assert.Equal(t, Entity{2, 0}, w.NewEntity())
 
-	w.RemEntity(Entity{1, 0})
+	assert.Equal(t, 0, int(w.entities[0].index))
+	assert.Equal(t, 1, int(w.entities[1].index))
+	assert.Equal(t, 2, int(w.entities[2].index))
+
+	assert.True(t, w.RemEntity(Entity{1, 0}))
+	assert.False(t, w.Alive(Entity{1, 0}))
+
+	assert.Equal(t, 0, int(w.entities[0].index))
+	assert.Equal(t, 1, int(w.entities[2].index))
+
 	assert.Equal(t, Entity{1, 1}, w.NewEntity())
+	assert.False(t, w.Alive(Entity{1, 0}))
+	assert.True(t, w.Alive(Entity{1, 1}))
+
+	assert.Equal(t, 2, int(w.entities[1].index))
+
+	assert.False(t, w.RemEntity(Entity{1, 0}))
+
+	assert.True(t, w.RemEntity(Entity{2, 0}))
+	assert.True(t, w.RemEntity(Entity{1, 1}))
+	assert.True(t, w.RemEntity(Entity{0, 0}))
+}
+
+func TestWorldComponents(t *testing.T) {
+	w := newWorld()
+
+	posID := RegisterComponent[position](w)
+	rotID := RegisterComponent[rotation](w)
+
+	e0 := w.NewEntity()
+	e1 := w.NewEntity()
+	e2 := w.NewEntity()
+
+	assert.Equal(t, 1, len(w.archetypes))
+
+	w.Add(e0, posID)
+	assert.Equal(t, 2, len(w.archetypes))
+	w.Add(e1, posID, rotID)
+	assert.Equal(t, 3, len(w.archetypes))
+	w.Add(e2, posID, rotID)
+	assert.Equal(t, 3, len(w.archetypes))
+
+	w.Remove(e2, posID)
+
+	maskNone := NewMask()
+	maskPos := NewMask(posID)
+	maskRot := NewMask(rotID)
+	maskPosRot := NewMask(posID, rotID)
+
+	archNone, ok := w.findArchetype(maskNone)
+	assert.True(t, ok)
+	archPos, ok := w.findArchetype(maskPos)
+	assert.True(t, ok)
+	archRot, ok := w.findArchetype(maskRot)
+	assert.True(t, ok)
+	archPosRot, ok := w.findArchetype(maskPosRot)
+	assert.True(t, ok)
+
+	assert.Equal(t, 0, int(w.archetypes[archNone].Len()))
+	assert.Equal(t, 1, int(w.archetypes[archPos].Len()))
+	assert.Equal(t, 1, int(w.archetypes[archRot].Len()))
+	assert.Equal(t, 1, int(w.archetypes[archPosRot].Len()))
+
+	w.Remove(e1, posID)
+
+	assert.Equal(t, 0, int(w.archetypes[archNone].Len()))
+	assert.Equal(t, 1, int(w.archetypes[archPos].Len()))
+	assert.Equal(t, 2, int(w.archetypes[archRot].Len()))
+	assert.Equal(t, 0, int(w.archetypes[archPosRot].Len()))
+
+	w.Add(e0, rotID)
+	assert.Equal(t, 0, int(w.archetypes[archPos].Len()))
+	assert.Equal(t, 1, int(w.archetypes[archPosRot].Len()))
+
+	w.Remove(e2, rotID)
+	// No-op add/remove
+	w.Add(e0)
+	w.Remove(e0)
+}
+
+func TestWorldGetComponents(t *testing.T) {
+	w := newWorld()
+
+	posID := RegisterComponent[position](w)
+	rotID := RegisterComponent[rotation](w)
+
+	e0 := w.NewEntity()
+	e1 := w.NewEntity()
+	e2 := w.NewEntity()
+
+	w.Add(e0, posID, rotID)
+	w.Add(e1, posID, rotID)
+	w.Add(e2, rotID)
+
+	assert.False(t, w.Has(e2, posID))
+	assert.True(t, w.Has(e2, rotID))
+
+	pos1 := (*position)(w.Get(e1, posID))
+	assert.Equal(t, &position{}, pos1)
+
+	pos1.X = 100
+	pos1.Y = 101
+
+	pos0 := (*position)(w.Get(e0, posID))
+	pos1 = (*position)(w.Get(e1, posID))
+	assert.Equal(t, &position{}, pos0)
+	assert.Equal(t, &position{100, 101}, pos1)
+
+	w.RemEntity(e0)
+
+	pos1 = (*position)(w.Get(e1, posID))
+	assert.Equal(t, &position{100, 101}, pos1)
+
+	pos2 := (*position)(w.Get(e2, posID))
+	assert.True(t, pos2 == nil)
+}
+
+func TestRegisterComponents(t *testing.T) {
+	world := NewWorld()
+
+	RegisterComponent[position](world)
+
+	assert.Equal(t, ID(0), ComponentID[position](world))
+	assert.Equal(t, ID(1), ComponentID[rotation](world))
 }