Skip to content

Commit

Permalink
Fixed incorrect work of the views and more tests added
Browse files Browse the repository at this point in the history
  • Loading branch information
sedyh committed Dec 28, 2021
1 parent b07c0ac commit e0b7efd
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 11 deletions.
15 changes: 11 additions & 4 deletions pkg/engine/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,20 @@ func makeEntity(w *world, components ...interface{}) *entity {
// entity.Get(&pos, &rad)
func (e *entity) Get(components ...interface{}) {
for _, component := range components {
componentValue := reflect.ValueOf(component).Elem()
componentType := componentValue.Type().Elem()
componentValue := reflect.ValueOf(component)
if componentValue.Kind() != reflect.Ptr {
panic(fmt.Sprintf("received entity component %s must be a pointer", typeName(componentValue.Type())))
}
componentValueElem := componentValue.Elem()
if componentValueElem.Kind() != reflect.Ptr {
panic(fmt.Sprintf("received entity component %s must be a pointer to pointer", typeName(componentValue.Type())))
}
componentType := componentValueElem.Type().Elem()
componentId := e.w.componentIds[componentType]
if e.mask.get(componentId) {
e.w.stores[componentId].get(e.id, componentValue)
e.w.stores[componentId].get(e.id, componentValueElem)
} else {
componentValue.Set(reflect.Zero(reflect.PtrTo(componentType)))
componentValueElem.Set(reflect.Zero(reflect.PtrTo(componentType)))
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/engine/mask.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package engine

import "fmt"

// mask is an implementation of an expanding bitmask.
type mask []uint64

Expand Down Expand Up @@ -32,3 +34,11 @@ func (m mask) contains(mask mask) bool {
}
return true
}

func (m mask) String() string {
str := ""
for _, bits := range m {
str += fmt.Sprintf("%064b", bits)
}
return str
}
16 changes: 15 additions & 1 deletion pkg/engine/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
// This is useful when you need to make requests to different sets
// of components in the same system.
type View interface {
Each(consumer func(entity Entity))
Filter() []Entity
}

Expand All @@ -24,14 +25,27 @@ func makeView(world *world, components ...interface{}) *view {

for _, component := range components {
componentType := reflect.TypeOf(component)
componentId := world.componentIds[componentType]
componentId, ok := world.componentIds[componentType]
if !ok {
continue
}
m.set(componentId)
}

return &view{w: world, mask: m}
}

// Each iterates all entities with the previously selected components.
func (v *view) Each(consumer func(entity Entity)) {
for _, e := range v.w.entities {
if e.mask.contains(v.mask) {
consumer(e)
}
}
}

// Filter returns a list of entities with the previously selected components for separate sorting and iteration.
// It is save to delete from here
func (v *view) Filter() []Entity {
entities := make([]Entity, 0, 2)
for _, e := range v.w.entities {
Expand Down
11 changes: 5 additions & 6 deletions pkg/engine/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,13 @@ func (w *world) Bounds() image.Rectangle {

// View creates a query to filter entities by their components.
func (w *world) View(components ...interface{}) View {
return makeView(w, components)
return makeView(w, components...)
}

// AddComponents registers the used components that will represent your object properties.
func (w *world) AddComponents(components ...interface{}) {
for _, component := range components {
componentValue := reflect.ValueOf(component)
componentType := componentValue.Type()
componentType := reflect.TypeOf(component)
if _, ok := w.componentIds[componentType]; ok {
continue
}
Expand Down Expand Up @@ -148,9 +147,9 @@ func (w *world) RemoveEntity(e Entity) {
w.entities[len(w.entities)-1] = nil
w.entities = w.entities[:len(w.entities)-1]

for i := 0; i < len(w.stores); i++ {
if v.mask.get(i) {
w.stores[i].rem(v.id)
for j := 0; j < len(w.stores); j++ {
if v.mask.get(j) {
w.stores[j].rem(v.id)
}
}
w.entitiesIds.rem(v.id)
Expand Down
90 changes: 90 additions & 0 deletions test/system_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/sedyh/mizu/pkg/engine"
"github.com/sedyh/mizu/test/helper"
)

type Position struct {
X, Y int
}

type Velocity struct {
X, Y int
}

type Gravity struct {
Value int
}

type First struct {
Value bool
}

type Ball struct {
First
Position
Velocity
Gravity
}

type Movement struct {
*Position
*Velocity
}

func (m *Movement) Update(_ engine.World) {
m.Position.X += m.Velocity.X
m.Position.Y += m.Velocity.Y
}

type Falling struct {
*Velocity
*Gravity
}

func (f *Falling) Update(_ engine.World) {
f.Velocity.Y += f.Gravity.Value
}

var _ = Describe("Game world", func() {
It("Should be able to create a system that traverses each entity", func() {
var world engine.World

// Run the world with two systems in two entities
helper.RunSingleSceneGame(func(w engine.World) {
w.AddComponents(First{}, Position{}, Velocity{}, Gravity{})
w.AddEntities(
&Ball{First{true}, Position{1, 5}, Velocity{-3, 4}, Gravity{2}},
&Ball{First{}, Position{-5, 3}, Velocity{1, 8}, Gravity{3}},
)
w.AddSystems(&Movement{}, &Falling{})
world = w
})

// Check that each system done it work right
world.View(First{}, Position{}, Velocity{}).Each(func(e engine.Entity) {
var first *First
var position *Position
var velocity *Velocity
e.Get(&first, &position, &velocity)
if first.Value {
Expect(*position).To(Equal(Position{-2, 9}))
Expect(*velocity).To(Equal(Velocity{-3, 6}))
}
})
world.View(First{}, Position{}, Velocity{}).Each(func(e engine.Entity) {
var first *First
var position *Position
var velocity *Velocity
e.Get(&first, &position, &velocity)
if !first.Value {
Expect(*position).To(Equal(Position{-4, 11}))
Expect(*velocity).To(Equal(Velocity{1, 11}))
}
})
})
})
74 changes: 74 additions & 0 deletions test/view_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/sedyh/mizu/pkg/engine"
"github.com/sedyh/mizu/test/helper"
)

var _ = Describe("Game world", func() {
It("Should be able to create the correct View", func() {
type ComponentA struct {
Value int
}
type ComponentB struct {
Value int
}
type ComponentC struct {
Value int
}
type ComponentD struct {
Value int
}
type EntityA struct {
ComponentA
ComponentB
ComponentC
}
type EntityB struct {
ComponentD
}
i := 0
helper.RunSingleSceneGame(func(w engine.World) {
w.AddComponents(ComponentA{}, ComponentB{}, ComponentC{}, ComponentD{})
w.AddEntities(&EntityA{ComponentA{}, ComponentB{}, ComponentC{}}, &EntityB{ComponentD{}})
for j := 0; j < 100; j++ {
// Each view should fire only one time per iteration
for _, e := range w.View(ComponentA{}).Filter() {
var a *ComponentA
e.Get(&a)
i++
}
for _, e := range w.View(ComponentA{}, ComponentB{}).Filter() {
var a *ComponentA
var b *ComponentB
e.Get(&a, &b)
i++
}
// The order of the components is not important
for _, e := range w.View(ComponentB{}, ComponentC{}).Filter() {
var b *ComponentB
var c *ComponentC
e.Get(&b, &c)
i++
}
for _, e := range w.View(ComponentC{}, ComponentB{}).Filter() {
var c *ComponentC
var b *ComponentB
e.Get(&c, &b)
i++
}
for _, e := range w.View(ComponentA{}, ComponentB{}, ComponentB{}).Filter() {
var a *ComponentA
var b *ComponentB
var c *ComponentC
e.Get(&c, &b, &a)
i++
}
}
})
Expect(i).To(Equal(500))
})
})

0 comments on commit e0b7efd

Please sign in to comment.