Skip to content

Commit

Permalink
Update donburi architecture (#81)
Browse files Browse the repository at this point in the history
update donburi architecture for #78
  • Loading branch information
yohamta authored Nov 6, 2022
1 parent e67fd23 commit d58ea16
Show file tree
Hide file tree
Showing 53 changed files with 269 additions and 241 deletions.
35 changes: 14 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,18 @@ entity = world.Create(Position, Velocity);
// which allows you to access the components that belong to the entity.
entry := world.Entry(entity)

position := donburi.Get[PositionData](entry, Position)
velocity := donburi.Get[VelocityData](entry, Velocity)
position.X += velocity.X
position.Y += velocity.y
```
// You can set or get the data via the ComponentType
Position.SetValue(math.Vec2{X: 10, Y: 20})
Velocity.SetValue(math.Vec2{X: 1, Y: 2})

We can define helper functions to get components for better readability. This was advice from [eliasdaler](https://github.com/eliasdaler).

```go
func GetPosition(entry *donburi.Entry) *PositionData {
return donburi.Get[PositionData](entry, Position)
}
position := Position.Get(entry)
velocity := Velocity.Get(entry)

func GetVelocity(entry *donburi.Entry) *VelocityData {
return donburi.Get[VelocityData](entry, Velocity)
}
position.X += velocity.X
position.Y += velocity.y
```

Components can be added and removed through Entry objects.
Components can be added and removed through `Entry` objects.

```go
// Fetch the first entity with PlayerTag component
Expand Down Expand Up @@ -183,22 +176,22 @@ query := query.NewQuery(
// In our query we can check if the entity has some of the optional components before attempting to retrieve them
query.EachEntity(world, func(entry *donburi.Entry) {
// We'll always be able to access Position and Size
position := donburi.Get[PositionData](entry, Position)
size := donburi.Get[SizeData](entry, Size)
position := Position.Get(entry)
size := Size.Get(entry)


if entry.HasComponent(Sprite) {
sprite := donburi.Get[SpriteData](entry, Sprite)
sprite := Sprite.Get(entry)
// .. do sprite things
}

if entry.HasComponent(Text) {
text := donburi.Get[TextData](entry, Text)
text := Text.Get(entry)
// .. do text things
}

if entry.HasComponent(Shape) {
shape := donburi.Get[ShapeData](entry, Shape)
shape := Shape.Get(entry)
// .. do shape things
}

Expand Down Expand Up @@ -354,7 +347,7 @@ transform.SetWorldScale(parent, dmath.Vec2{X: 2, Y: 3})

// setup child
child := w.Entry(w.Create(transform.Transform))
donburi.SetValue(child, transform.Transform, transform.TransformData{
transform.Transform.SetValue(child, transform.TransformData{
LocalPosition: dmath.Vec2{X: 1, Y: 2},
LocalRotation: 90,
LocalScale: dmath.Vec2{X: 2, Y: 3},
Expand Down
106 changes: 98 additions & 8 deletions component.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,110 @@
package donburi

import "github.com/yohamta/donburi/internal/component"
import (
"fmt"
"reflect"
"unsafe"

// ComponentType represents a component type.
// It is used to add components to entities, and to filter entities based on their components.
// It contains a function that returns a pointer to a new component.
type ComponentType = component.ComponentType
"github.com/yohamta/donburi/internal/component"
)

// IComponentType is an interface for component types.
type IComponentType = component.IComponentType

// NewComponentType creates a new component type.
// The function is used to create a new component of the type.
// It receives a function that returns a pointer to a new component.
// The first argument is a default value of the component.
func NewComponentType[T any](opts ...interface{}) *ComponentType {
func NewComponentType[T any](opts ...interface{}) *ComponentType[T] {
var t T
if len(opts) == 0 {
return component.NewComponentType(t, nil)
return newComponentType(t, nil)
}
return newComponentType(t, opts[0])
}

// CompnentType represents a type of component. It is used to identify
// a component when getting or setting components of an entity.
type ComponentType[T any] struct {
id component.ComponentTypeId
typ reflect.Type
name string
defaultVal interface{}
}

// Get returns component data from the entry.
func (c *ComponentType[T]) Get(entry *Entry) *T {
return (*T)(entry.Component(c))
}

// Set sets component data to the entry.
func (c *ComponentType[T]) Set(entry *Entry, compoennt *T) {
entry.SetComponent(c, unsafe.Pointer(compoennt))
}

// SetValue sets the value of the component.
func (c *ComponentType[T]) SetValue(entry *Entry, value T) {
comp := c.Get(entry)
*comp = value
}

// String returns the component type name.
func (c *ComponentType[T]) String() string {
return c.name
}

// SetName sets the component type name.
func (c *ComponentType[T]) SetName(name string) *ComponentType[T] {
c.name = name
return c
}

// Name returns the component type name.
func (c *ComponentType[T]) Name() string {
return c.name
}

// Id returns the component type id.
func (c *ComponentType[T]) Id() component.ComponentTypeId {
return c.id
}

func (c *ComponentType[T]) New() unsafe.Pointer {
val := reflect.New(c.typ)
v := reflect.Indirect(val)
ptr := unsafe.Pointer(v.UnsafeAddr())
if c.defaultVal != nil {
c.setDefaultVal(ptr)
}
return ptr
}

func (c *ComponentType[T]) setDefaultVal(ptr unsafe.Pointer) {
v := reflect.Indirect(reflect.ValueOf(c.defaultVal))
reflect.NewAt(c.typ, ptr).Elem().Set(v)
}

func (c *ComponentType[T]) validateDefaultVal() {
if !reflect.TypeOf(c.defaultVal).AssignableTo(c.typ) {
err := fmt.Sprintf("default value is not assignable to component type: %s", c.name)
panic(err)
}
}

var nextComponentTypeId component.ComponentTypeId = 1

// NewComponentType creates a new component type.
// The argument is a struct that represents a data of the component.
func newComponentType[T any](s T, defaultVal interface{}) *ComponentType[T] {
componentType := &ComponentType[T]{
id: nextComponentTypeId,
typ: reflect.TypeOf(s),
name: reflect.TypeOf(s).Name(),
defaultVal: defaultVal,
}
if defaultVal != nil {
componentType.validateDefaultVal()
}
return component.NewComponentType(t, opts[0])
nextComponentTypeId++
return componentType
}
4 changes: 2 additions & 2 deletions internal/component/component_test.go → component_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package component
package donburi

import "testing"

func TestComponent(t *testing.T) {
c := NewComponentType(testComponentData{}, nil)
c := NewComponentType[testComponentData](testComponentData{}, nil)
if c.String() != "testComponentData" {
t.Errorf("expected name testComponentData, got %s", c.String())
}
Expand Down
5 changes: 2 additions & 3 deletions ecs/ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/internal/component"
"github.com/yohamta/donburi/query"
)

Expand Down Expand Up @@ -79,14 +78,14 @@ func (ecs *ECS) Draw(screen *ebiten.Image) {
}

// Create creates a new entity
func (ecs *ECS) Create(l LayerID, components ...*donburi.ComponentType) donburi.Entity {
func (ecs *ECS) Create(l LayerID, components ...donburi.IComponentType) donburi.Entity {
entry := ecs.World.Entry(ecs.World.Create(components...))
entry.AddComponent(ecs.getLayer(l).tag)
return entry.Entity()
}

// Create creates a new entity
func (ecs *ECS) CreateMany(l LayerID, n int, components ...*component.ComponentType) []donburi.Entity {
func (ecs *ECS) CreateMany(l LayerID, n int, components ...donburi.IComponentType) []donburi.Entity {
comps := append(components, ecs.getLayer(l).tag)
return ecs.World.CreateMany(n, comps...)
}
Expand Down
2 changes: 1 addition & 1 deletion ecs/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var (

type layer struct {
id LayerID
tag *donburi.ComponentType
tag donburi.IComponentType
}

func getLayer(layerID LayerID) *layer {
Expand Down
22 changes: 11 additions & 11 deletions entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,28 @@ type Entry struct {
}

// Get returns the component from the entry
func Get[T any](e *Entry, ctype *component.ComponentType) *T {
func Get[T any](e *Entry, ctype component.IComponentType) *T {
return (*T)(e.Component(ctype))
}

// Add adds the component to the entry.
func Add[T any](e *Entry, ctype *component.ComponentType, component *T) {
func Add[T any](e *Entry, ctype component.IComponentType, component *T) {
e.AddComponent(ctype, unsafe.Pointer(component))
}

// Set sets the comopnent of the entry.
func Set[T any](e *Entry, ctype *component.ComponentType, component *T) {
func Set[T any](e *Entry, ctype component.IComponentType, component *T) {
e.SetComponent(ctype, unsafe.Pointer(component))
}

// SetValue sets the value of the component.
func SetValue[T any](e *Entry, ctype *component.ComponentType, value T) {
func SetValue[T any](e *Entry, ctype component.IComponentType, value T) {
c := Get[T](e, ctype)
*c = value
}

// Remove removes the component from the entry.
func Remove(e *Entry, ctype *component.ComponentType) {
func Remove[T any](e *Entry, ctype component.IComponentType) {
e.RemoveComponent(ctype)
}

Expand All @@ -64,21 +64,21 @@ func (e *Entry) Entity() Entity {
}

// Component returns the component.
func (e *Entry) Component(ctype *component.ComponentType) unsafe.Pointer {
func (e *Entry) Component(ctype component.IComponentType) unsafe.Pointer {
c := e.loc.Component
a := e.loc.Archetype
return e.World.components.Storage(ctype).Component(a, c)
}

// SetComponent sets the component.
func (e *Entry) SetComponent(ctype *component.ComponentType, component unsafe.Pointer) {
func (e *Entry) SetComponent(ctype component.IComponentType, component unsafe.Pointer) {
c := e.loc.Component
a := e.loc.Archetype
e.World.components.Storage(ctype).SetComponent(a, c, component)
}

// AddComponent adds the component to the entity.
func (e *Entry) AddComponent(ctype *component.ComponentType, components ...unsafe.Pointer) {
func (e *Entry) AddComponent(ctype component.IComponentType, components ...unsafe.Pointer) {
if len(components) > 1 {
panic("AddComponent: component argument must be a single value")
}
Expand All @@ -98,7 +98,7 @@ func (e *Entry) AddComponent(ctype *component.ComponentType, components ...unsaf
}

// RemoveComponent removes the component from the entity.
func (e *Entry) RemoveComponent(ctype *component.ComponentType) {
func (e *Entry) RemoveComponent(ctype component.IComponentType) {
if !e.Archetype().Layout().HasComponent(ctype) {
return
}
Expand All @@ -107,7 +107,7 @@ func (e *Entry) RemoveComponent(ctype *component.ComponentType) {
a := e.loc.Archetype

base_layout := e.World.archetypes[a].Layout().Components()
target_layout := make([]*component.ComponentType, 0, len(base_layout)-1)
target_layout := make([]component.IComponentType, 0, len(base_layout)-1)
for _, c2 := range base_layout {
if c2 == ctype {
continue
Expand Down Expand Up @@ -138,7 +138,7 @@ func (e *Entry) Archetype() *storage.Archetype {
}

// HasComponent returns true if the entity has the given component type.
func (e *Entry) HasComponent(componentType *component.ComponentType) bool {
func (e *Entry) HasComponent(componentType component.IComponentType) bool {
return e.Archetype().Layout().HasComponent(componentType)
}

Expand Down
4 changes: 0 additions & 4 deletions examples/bunnymark/component/hue.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,3 @@ type HueData struct {
}

var Hue = donburi.NewComponentType[HueData]()

func GetHue(entry *donburi.Entry) *HueData {
return donburi.Get[HueData](entry, Hue)
}
4 changes: 0 additions & 4 deletions examples/bunnymark/component/position.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,3 @@ type PositionData struct {
}

var Position = donburi.NewComponentType[PositionData]()

func GetPosition(entry *donburi.Entry) *PositionData {
return donburi.Get[PositionData](entry, Position)
}
4 changes: 0 additions & 4 deletions examples/bunnymark/component/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,3 @@ type SettingsData struct {
}

var Settings = donburi.NewComponentType[SettingsData]()

func GetSettings(entry *donburi.Entry) *SettingsData {
return donburi.Get[SettingsData](entry, Settings)
}
4 changes: 0 additions & 4 deletions examples/bunnymark/component/sprite.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,3 @@ type SpriteData struct {
}

var Sprite = donburi.NewComponentType[SpriteData]()

func GetSprite(entry *donburi.Entry) *SpriteData {
return donburi.Get[SpriteData](entry, Sprite)
}
4 changes: 0 additions & 4 deletions examples/bunnymark/component/velocity.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,3 @@ type VelocityData struct {
}

var Velocity = donburi.NewComponentType[VelocityData]()

func GetVelocity(entry *donburi.Entry) *VelocityData {
return donburi.Get[VelocityData](entry, Velocity)
}
6 changes: 3 additions & 3 deletions examples/bunnymark/system/bounce.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ func NewBounce(bounds *image.Rectangle) *Bounce {

func (b *Bounce) Update(w donburi.World) {
b.query.EachEntity(w, func(entry *donburi.Entry) {
position := component.GetPosition(entry)
velocity := component.GetVelocity(entry)
sprite := component.GetSprite(entry)
position := component.Position.Get(entry)
velocity := component.Velocity.Get(entry)
sprite := component.Sprite.Get(entry)

sw, sh := float64(b.bounds.Dx()), float64(b.bounds.Dy())
iw, ih := float64(sprite.Image.Bounds().Dx()), float64(sprite.Image.Bounds().Dy())
Expand Down
4 changes: 2 additions & 2 deletions examples/bunnymark/system/gravity.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func NewGravity() *Gravity {

func (g *Gravity) Update(w donburi.World) {
g.query.EachEntity(w, func(entry *donburi.Entry) {
gravity := component.GetGravity(entry)
velocity := component.GetVelocity(entry)
gravity := component.Gravity.Get(entry)
velocity := component.Velocity.Get(entry)

velocity.Y += gravity.Value
})
Expand Down
2 changes: 1 addition & 1 deletion examples/bunnymark/system/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (m *Metrics) Update(w donburi.World) {
if m.settings == nil {
query := query.NewQuery(filter.Contains(component.Settings))
query.EachEntity(w, func(entry *donburi.Entry) {
m.settings = component.GetSettings(entry)
m.settings = component.Settings.Get(entry)
})
}
select {
Expand Down
Loading

0 comments on commit d58ea16

Please sign in to comment.