Skip to content

Commit

Permalink
Add Component.First and Component.EachEntity methods (#87)
Browse files Browse the repository at this point in the history
Add component.First and component.EachEntity methods
  • Loading branch information
yohamta authored Dec 3, 2022
1 parent ffbc357 commit 4326700
Show file tree
Hide file tree
Showing 43 changed files with 252 additions and 211 deletions.
48 changes: 40 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ var Velocity = donburi.NewComponentType[VelocityData]()

// Create an entity by specifying components that the entity will have.
// Component data will be initialized by default value of the struct.
entity = world.Create(Position, Velocity);
entity = world.Create(Position, Velocity)

// We can use entity (it's a wrapper of int64) to get an Entry object from World
// which allows you to access the components that belong to the entity.
Expand All @@ -101,10 +101,10 @@ Components can be added and removed through `Entry` objects.

```go
// Fetch the first entity with PlayerTag component
query := query.NewQuery(filter.Contains(PlayerTag))
query := donburi.NewQuery(filter.Contains(PlayerTag))
// Query.FirstEntity() returns only the first entity that
// matches the query.
if entry, ok := query.FirstEntity(world); ok {
if entry, ok := donburi.FirstEntity(world); ok {
donburi.Add(entry, Position, &PositionData{
X: 100,
Y: 100,
Expand All @@ -126,13 +126,46 @@ if SomeLogic.IsDead(world, someEntity) {
}
```

Entities can be retrieved using the `First` and `EachEntity` methods of Components as follows:

```go
// GameState Component
type GameStateData struct {
// .. some data
}
var GameState = donburi.NewComponentType[GameStateData]()

// Bullet Component
type BulletData struct {
// .. some data
}
var Bullet = donburi.NewComponentType[BulletData]()

// Init the world and create entities
world := donburi.NewWorld()
world.Create(GameState)
world.CreateMany(100, Bullet)

// Query the first GameState entity
if entry, ok := GameState.First(world); ok {
gameState := GameState.Get(entry)
// .. do stuff with the gameState entity
}

// Query all Bullet entities
Bullet.EachEntity(world, func(entry *donburi.Entry) {
bullet := Bullet.Get(entry)
// .. do stuff with the bullet entity
})
```

### Queries

Queries allow for high performance and expressive iteration through the entities in a world, to get component references, test if an entity has a component or to add and remove components.

```go
// Define a query by declaring what componet you want to find.
query := query.NewQuery(filter.Contains(Position, Velocity))
query := donburi.NewQuery(filter.Contains(Position, Velocity))

// Iterate through the entities found in the world
query.EachEntity(world, func(entry *donburi.Entry) {
Expand All @@ -151,7 +184,7 @@ For example:

```go
// This query retrieves entities that have an NpcTag and no Position component.
query := query.NewQuery(filter.And(
query := donburi.NewQuery(filter.And(
filter.Contains(NpcTag),
filter.Not(filter.Contains(Position))))
```
Expand All @@ -162,7 +195,7 @@ For example:

```go
// We have a query for all entities that have Position and Size, but also any of Sprite, Text or Shape.
query := query.NewQuery(
query := donburi.NewQuery(
filter.And(
filter.Contains(Position, Size),
filter.Or(
Expand Down Expand Up @@ -218,8 +251,7 @@ var EnemyTag = donburi.NewTag()
world.CreateMany(100, EnemyTag, Position, Velocity)

// Search entities with EnemyTag
query := query.NewQuery(filter.Contains(EnemyTag))
query.EachEntity(world, func(entry *donburi.Entry) {
EnemyTag.EachEntity(world, func(entry *donburi.Entry) {
// Perform some operation on the Entities with the EnemyTag component.
}
```
Expand Down
15 changes: 14 additions & 1 deletion component.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import (
"reflect"
"unsafe"

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

// IComponentType is an interface for component types.
Expand All @@ -30,6 +31,7 @@ type ComponentType[T any] struct {
typ reflect.Type
name string
defaultVal interface{}
query *Query
}

// Get returns component data from the entry.
Expand All @@ -42,6 +44,16 @@ func (c *ComponentType[T]) Set(entry *Entry, compoennt *T) {
entry.SetComponent(c, unsafe.Pointer(compoennt))
}

// EachEntity iterates over the entities that have the component.
func (c *ComponentType[T]) EachEntity(w World, callback func(*Entry)) {
c.query.EachEntity(w, callback)
}

// FirstEntity returns the first entity that has the component.
func (c *ComponentType[T]) FirstEntity(w World) (*Entry, bool) {
return c.query.FirstEntity(w)
}

// SetValue sets the value of the component.
func (c *ComponentType[T]) SetValue(entry *Entry, value T) {
comp := c.Get(entry)
Expand Down Expand Up @@ -102,6 +114,7 @@ func newComponentType[T any](s T, defaultVal interface{}) *ComponentType[T] {
name: reflect.TypeOf(s).Name(),
defaultVal: defaultVal,
}
componentType.query = NewQuery(filter.Contains(componentType))
if defaultVal != nil {
componentType.validateDefaultVal()
}
Expand Down
File renamed without changes.
7 changes: 3 additions & 4 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/query"
)

// LayerID is used to specify a layer.
Expand All @@ -25,12 +24,12 @@ type ECS struct {
}

// NewQuery creates a new query.
func NewQuery(l LayerID, f filter.LayoutFilter) *query.Query {
func NewQuery(l LayerID, f filter.LayoutFilter) *donburi.Query {
layerFilter := filter.Contains(getLayer(l).tag)
if f == nil {
return query.NewQuery(layerFilter)
return donburi.NewQuery(layerFilter)
}
return query.NewQuery(filter.And(layerFilter, f))
return donburi.NewQuery(filter.And(layerFilter, f))
}

// NewECS creates a new ECS with the specified world.
Expand Down
3 changes: 1 addition & 2 deletions ecs/ecs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

func TestECS(t *testing.T) {
Expand Down Expand Up @@ -124,7 +123,7 @@ type testSystem struct {
DrawImage *ebiten.Image
UpdateCount int
DrawCount int
Query *query.Query
Query *donburi.Query
QueryCountUpdate int
QueryCountDraw int
}
Expand Down
2 changes: 1 addition & 1 deletion entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"unsafe"

"github.com/yohamta/donburi/internal/component"
"github.com/yohamta/donburi/component"
"github.com/yohamta/donburi/internal/entity"
"github.com/yohamta/donburi/internal/storage"
)
Expand Down
5 changes: 2 additions & 3 deletions examples/bunnymark/system/bounce.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@ import (
"github.com/yohamta/donburi/examples/bunnymark/component"
"github.com/yohamta/donburi/examples/bunnymark/helper"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

type Bounce struct {
bounds *image.Rectangle
query *query.Query
query *donburi.Query
}

func NewBounce(bounds *image.Rectangle) *Bounce {
return &Bounce{
bounds: bounds,
query: query.NewQuery(filter.Contains(
query: donburi.NewQuery(filter.Contains(
component.Position,
component.Velocity,
component.Sprite,
Expand Down
5 changes: 2 additions & 3 deletions examples/bunnymark/system/gravity.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import (
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/examples/bunnymark/component"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

type Gravity struct {
query *query.Query
query *donburi.Query
}

func NewGravity() *Gravity {
return &Gravity{
query: query.NewQuery(filter.Contains(component.Velocity, component.Gravity)),
query: donburi.NewQuery(filter.Contains(component.Velocity, component.Gravity)),
}
}

Expand Down
9 changes: 4 additions & 5 deletions examples/bunnymark/system/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/text"
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/examples/bunnymark/component"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
"golang.org/x/image/colornames"
"golang.org/x/image/font/basicfont"
)
Expand All @@ -29,10 +27,11 @@ func NewMetrics(bounds *image.Rectangle) *Metrics {

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) {
if entry, ok := component.Settings.FirstEntity(w); ok {
m.settings = component.Settings.Get(entry)
})
} else {
panic("no settings")
}
}
select {
case <-m.settings.Ticker.C:
Expand Down
5 changes: 2 additions & 3 deletions examples/bunnymark/system/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import (
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/examples/bunnymark/component"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

type Render struct {
query *query.Query
query *donburi.Query
}

func NewRender() *Render {
return &Render{
query: query.NewQuery(filter.Contains(
query: donburi.NewQuery(filter.Contains(
component.Position,
component.Hue,
component.Sprite,
Expand Down
9 changes: 4 additions & 5 deletions examples/bunnymark/system/spawn.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/examples/bunnymark/component"
"github.com/yohamta/donburi/examples/bunnymark/helper"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

type Spawn struct {
Expand Down Expand Up @@ -45,10 +43,11 @@ func (s *Spawn) Update(w donburi.World) {

func (s *Spawn) addBunnies(w donburi.World) {
if s.settings == nil {
query := query.NewQuery(filter.Contains(component.Settings))
query.EachEntity(w, func(entry *donburi.Entry) {
if entry, ok := component.Settings.FirstEntity(w); ok {
s.settings = component.Settings.Get(entry)
})
} else {
panic("no settings")
}
}

entities := w.CreateMany(
Expand Down
5 changes: 2 additions & 3 deletions examples/bunnymark/system/velocity.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import (
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/examples/bunnymark/component"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

type Velocity struct {
query *query.Query
query *donburi.Query
}

func NewVelocity() *Velocity {
return &Velocity{
query: query.NewQuery(filter.Contains(component.Position, component.Velocity)),
query: donburi.NewQuery(filter.Contains(component.Position, component.Velocity)),
}
}

Expand Down
5 changes: 2 additions & 3 deletions examples/bunnymark_ecs/system/bounce.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ import (
"github.com/yohamta/donburi/examples/bunnymark_ecs/component"
"github.com/yohamta/donburi/examples/bunnymark_ecs/helper"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

type bounce struct {
bounds *image.Rectangle
query *query.Query
query *donburi.Query
}

func NewBounce(bounds *image.Rectangle) *bounce {
return &bounce{
bounds: bounds,
query: query.NewQuery(
query: donburi.NewQuery(
filter.Contains(
component.Position,
component.Velocity,
Expand Down
5 changes: 2 additions & 3 deletions examples/bunnymark_ecs/system/gravity.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import (
"github.com/yohamta/donburi/ecs"
"github.com/yohamta/donburi/examples/bunnymark_ecs/component"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

type gravity struct {
query *query.Query
query *donburi.Query
}

var Gravity *gravity = &gravity{
query: query.NewQuery(
query: donburi.NewQuery(
filter.Contains(
component.Velocity,
component.Gravity,
Expand Down
8 changes: 2 additions & 6 deletions examples/bunnymark_ecs/system/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/text"
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/ecs"
"github.com/yohamta/donburi/examples/bunnymark_ecs/component"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
"golang.org/x/image/colornames"
"golang.org/x/image/font/basicfont"
)
Expand All @@ -30,10 +27,9 @@ func NewMetrics(bounds *image.Rectangle) *Metrics {

func (m *Metrics) Update(ecs *ecs.ECS) {
if m.settings == nil {
query := query.NewQuery(filter.Contains(component.Settings))
query.EachEntity(ecs.World, func(entry *donburi.Entry) {
if entry, ok := component.Settings.FirstEntity(ecs.World); ok {
m.settings = component.Settings.Get(entry)
})
}
}
select {
case <-m.settings.Ticker.C:
Expand Down
3 changes: 1 addition & 2 deletions examples/bunnymark_ecs/system/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ import (
"github.com/yohamta/donburi/examples/bunnymark_ecs/component"
"github.com/yohamta/donburi/examples/bunnymark_ecs/layers"
"github.com/yohamta/donburi/filter"
"github.com/yohamta/donburi/query"
)

type render struct {
query *query.Query
query *donburi.Query
}

var Render = &render{
Expand Down
Loading

0 comments on commit 4326700

Please sign in to comment.