Skip to content

Commit

Permalink
Merge pull request #15 from yohamta/fix-issue14
Browse files Browse the repository at this point in the history
Fix a bug in moving components between archetypes
  • Loading branch information
yohamta authored Sep 20, 2022
2 parents d1a0e9d + 7d80530 commit 948168b
Show file tree
Hide file tree
Showing 21 changed files with 661 additions and 319 deletions.
2 changes: 1 addition & 1 deletion entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
type Entry struct {
id entity.EntityId
entity Entity
loc *storage.EntityLocation
loc *storage.Location
world *world
}

Expand Down
13 changes: 8 additions & 5 deletions internal/storage/archetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ type ArchetypeIndex int
type Archetype struct {
index ArchetypeIndex
entities []entity.Entity
layout *EntityLayout
layout *Layout
}

// NewArchetype creates a new archetype.
func NewArchetype(index ArchetypeIndex, layout *EntityLayout) *Archetype {
func NewArchetype(index ArchetypeIndex, layout *Layout) *Archetype {
return &Archetype{
index: index,
entities: make([]entity.Entity, 0, 256),
Expand All @@ -25,7 +25,7 @@ func NewArchetype(index ArchetypeIndex, layout *EntityLayout) *Archetype {
}

// Layout is a collection of archetypes for a specific layout of components.
func (archetype *Archetype) Layout() *EntityLayout {
func (archetype *Archetype) Layout() *Layout {
return archetype.layout
}

Expand All @@ -42,8 +42,11 @@ func (archetype *Archetype) SwapRemove(entity_index int) entity.Entity {
return removed
}

// Matches returns true if the given components match the archetype.
func (archetype *Archetype) MatchesLayout(components []*component.ComponentType) bool {
// LayoutMatches returns true if the given layout matches this archetype.
func (archetype *Archetype) LayoutMatches(components []*component.ComponentType) bool {
if len(archetype.layout.Components()) != len(components) {
return false
}
for _, componentType := range components {
if !archetype.layout.HasComponent(componentType) {
return false
Expand Down
50 changes: 50 additions & 0 deletions internal/storage/archetype_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package storage

import (
"testing"

"github.com/yohamta/donburi/internal/component"
)

func TestMatchesLayout(t *testing.T) {
var (
ca = component.NewComponentType(struct{}{})
cb = component.NewComponentType(struct{}{})
)

cmps := []*component.ComponentType{ca, cb}
archetype := NewArchetype(0, NewLayout(cmps))
if !archetype.LayoutMatches(cmps) {
t.Errorf("archetype should match the layout")
}
}

func TestPushEntity(t *testing.T) {
var (
ca = component.NewComponentType(struct{}{})
cb = component.NewComponentType(struct{}{})
)

cmps := []*component.ComponentType{ca, cb}
archetype := NewArchetype(0, NewLayout(cmps))

archetype.PushEntity(0)
archetype.PushEntity(1)
archetype.PushEntity(2)

if len(archetype.Entities()) != 3 {
t.Errorf("archetype should have 3 entities")
}

archetype.SwapRemove(1)
if len(archetype.Entities()) != 2 {
t.Errorf("archetype should have 2 entities")
}

expected := []int{0, 2}
for i, entity := range archetype.Entities() {
if int(entity) != expected[i] {
t.Errorf("archetype should have entity %d", expected[i])
}
}
}
19 changes: 14 additions & 5 deletions internal/storage/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ type ComponentIndex int

// Components is a structure that stores data of components.
type Components struct {
storages []*SimpleStorage
storages []*Storage
// TODO: optimize to use slice instead of map for performance
componentIndices map[ArchetypeIndex]ComponentIndex
}

// NewComponents creates a new empty structure that stores data of components.
func NewComponents() *Components {
return &Components{
storages: make([]*SimpleStorage, 512), // TODO: expand as the number of component types increases
storages: make([]*Storage, 512), // TODO: expand as the number of component types increases
componentIndices: make(map[ArchetypeIndex]ComponentIndex),
}
}
Expand All @@ -25,7 +26,7 @@ func NewComponents() *Components {
func (cs *Components) PushComponents(components []*component.ComponentType, archetypeIndex ArchetypeIndex) ComponentIndex {
for _, componentType := range components {
if v := cs.storages[componentType.Id()]; v == nil {
cs.storages[componentType.Id()] = NewSimpleStorage()
cs.storages[componentType.Id()] = NewStorage()
}
cs.storages[componentType.Id()].PushComponent(componentType, archetypeIndex)
}
Expand All @@ -37,12 +38,20 @@ func (cs *Components) PushComponents(components []*component.ComponentType, arch
return cs.componentIndices[archetypeIndex]
}

// MoveComponent moves the pointer to data of the component in the archetype.
func (cs *Components) MoveComponent(c *component.ComponentType, src ArchetypeIndex, idx ComponentIndex, dst ArchetypeIndex) {
storage := cs.Storage(c)
storage.MoveComponent(src, idx, dst)
cs.componentIndices[src]--
cs.componentIndices[dst]++
}

// Storage returns the pointer to data of the component in the archetype.
func (cs *Components) Storage(c *component.ComponentType) *SimpleStorage {
func (cs *Components) Storage(c *component.ComponentType) *Storage {
if storage := cs.storages[c.Id()]; storage != nil {
return storage
}
cs.storages[c.Id()] = NewSimpleStorage()
cs.storages[c.Id()] = NewStorage()
return cs.storages[c.Id()]
}

Expand Down
79 changes: 79 additions & 0 deletions internal/storage/components_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package storage

import (
"testing"

"github.com/yohamta/donburi/internal/component"
)

func TestComponents(t *testing.T) {
type ComponentData struct {
ID string
}
var (
ca = component.NewComponentType(ComponentData{})
cb = component.NewComponentType(ComponentData{})
)

components := NewComponents()

tests := []*struct {
layout *Layout
archIdx ArchetypeIndex
compIdx ComponentIndex
ID string
}{
{
NewLayout([]*component.ComponentType{ca}),
0,
0,
"a",
},
{
NewLayout([]*component.ComponentType{ca, cb}),
1,
1,
"b",
},
}

for _, tt := range tests {
tt.compIdx = components.PushComponents(tt.layout.Components(), tt.archIdx)
}

for _, tt := range tests {
for _, comp := range tt.layout.Components() {
st := components.Storage(comp)
if !st.Contains(tt.archIdx, tt.compIdx) {
t.Errorf("storage should contain the component at %d, %d", tt.archIdx, tt.compIdx)
}
dat := (*ComponentData)(st.Component(tt.archIdx, tt.compIdx))
dat.ID = tt.ID
}
}

target := tests[0]
storage := components.Storage(ca)

var srcArchIdx ArchetypeIndex = target.archIdx
var dstArchIdx ArchetypeIndex = 1

components.MoveComponent(ca, srcArchIdx, target.compIdx, dstArchIdx)

if storage.Contains(srcArchIdx, target.compIdx) {
t.Errorf("storage should not contain the component at %d, %d", target.archIdx, target.compIdx)
}
if components.componentIndices[srcArchIdx] != -1 {
t.Errorf("component index should be -1 at %d but %d", srcArchIdx, components.componentIndices[srcArchIdx])
}

newCompIdx := components.componentIndices[dstArchIdx]
if !storage.Contains(dstArchIdx, newCompIdx) {
t.Errorf("storage should contain the component at %d, %d", dstArchIdx, target.compIdx)
}

dat := (*ComponentData)(storage.Component(dstArchIdx, newCompIdx))
if dat.ID != target.ID {
t.Errorf("component should have ID %s", target.ID)
}
}
41 changes: 0 additions & 41 deletions internal/storage/entity_layout.go

This file was deleted.

17 changes: 0 additions & 17 deletions internal/storage/entity_location.go

This file was deleted.

59 changes: 59 additions & 0 deletions internal/storage/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package storage

import (
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/internal/component"
)

type ArchetypeIterator struct {
current int
values []ArchetypeIndex
}

func (it *ArchetypeIterator) HasNext() bool {
return it.current < len(it.values)
}

func (it *ArchetypeIterator) Next() ArchetypeIndex {
val := it.values[it.current]
it.current++
return val
}

// Index is a structure that indexes archetypes by their component types.
type Index struct {
layouts [][]*component.ComponentType
iterator *ArchetypeIterator
}

// NewIndex creates a new search index.
func NewIndex() *Index {
return &Index{
layouts: [][]*component.ComponentType{},
iterator: &ArchetypeIterator{
current: 0,
},
}
}

// Push adds an archetype to the search index.
func (idx *Index) Push(layout *Layout) {
idx.layouts = append(idx.layouts, layout.Components())
}

// SearchFrom searches for archetypes that match the given filter from the given index.
func (idx *Index) SearchFrom(f filter.LayoutFilter, start int) *ArchetypeIterator {
idx.iterator.current = 0
idx.iterator.values = []ArchetypeIndex{}
for i := start; i < len(idx.layouts); i++ {
if f.MatchesLayout(idx.layouts[i]) {
idx.iterator.values = append(idx.iterator.values, ArchetypeIndex(i))
}
}
return idx.iterator
}

// Search searches for archetypes that match the given filter.
func (idx *Index) Search(filter filter.LayoutFilter) *ArchetypeIterator {
return idx.SearchFrom(filter, 0)
}
Loading

0 comments on commit 948168b

Please sign in to comment.