Donburi is just another Entity Component System library for Ebiten inspired by legion.

donburi Donburi

Donburi is just another Entity Component System library for Ebiten inspired by legion.

It aims to be a feature rich and high performance ECS Library.

Contents

Features

  • It introduces the concept of Archetype, which allows us to query entities very efficiently based on the components layout.
  • It is possible to combine And, Or, and Not conditions to perform complex queries for components.
  • It avoids reflection on every frame and uses unsafe.Pointer for performance.
  • Ability to dynamically add or remove components from an entity

There are many features that need to be added in the future (e.g., parent-child relationship, event-notification system etc).

Examples

To check all examples, visit this page.

The bunnymark example was adapted from mizu's code, which is made by sedyh.

Installation

go get github.com/yohamta/donburi

Getting Started

Worlds

import "github.com/yohamta/donburi"

world := donburi.NewWorld()

Entities can be inserted via either Create (for a single entity) or CreateMany (for a collection of entities with the same component types). The world will create a unique ID for each entity upon insertion that you can use to refer to that entity later.

// Component is any struct that holds some kind of data.
type PositionData struct {
  X, Y float64
}

type VelocityData struct {
  X, Y float64
}

// ComponentType represents kind of component which is used to create or query entities.
var Position = donburi.NewComponentType(PositionData{})
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);

// You 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.
entry := world.Entry(entity)

position := (*component.PositionData)(entry.Component(component.Position))
velocity := (*component.VelocityData)(entry.Component(component.Velocity))
position.X += velocity.X
position.Y += velocity.y

You can define helper functions to get components for better readability. This was advice from eliasdaler.

func GetPositionData(entry *donburi.Entry) *PositionData {
  return (*PositionData)(entry.Component(Position))
}

func GetVelocityData(entry *donburi.Entry) *VelocityData {
  return (*VelocityData)(entry.Component(Velocity))
}

Queries

Queries allow for high performance and expressive iteration through the entities in a world, to find out what types of components are attached to it, to get component references, or to add and remove components.

You can search for entities which have all of a set of components.

// You can define a query by declaring what componet you want to find.
query := query.NewQuery(filter.Contains(component.Position, component.Velocity))

// You can then iterate through the entity found in the world
query.EachEntity(world, func(entry *donburi.Entry) {
  // An entry is an accessor to entity and its components.
  var position *component.PositionData = (*component.PositionData)(entry.Component(component.Position))
  var velocity *component.VelocityData = (*component.VelocityData)(entry.Component(component.Velocity))
  
  position.X += velocity.X
  position.Y += velocity.Y
})

There are other types of filters such as And, Or, Exact and Not. You can combine them to find the target entities.

For example:

// This query retrieves entities that have an NpcTag and no Position component.
query := query.NewQuery(filter.And(
  filter.Contains(NpcTag),
  filter.Not(filter.Contains(Position))))

Systems

As of today, there is no function for the concept of "Systems" in ECS. It is assumed that operations are performed on entities using queries.

Comments
  • Add error return values to a range of methods

    Add error return values to a range of methods

    I think we need to return potential errors from these two (there might be others but so far these are the ones I've encountered)

    func (e *Entry) Get[T any](e *Entry, ctype *component.ComponentType) (*T, error)

    func (cs *SimpleStorage) Component(archetypeIndex ArchetypeIndex, componentIndex ComponentIndex) (unsafe.Pointer, error)

    My reasons:

    I have a System (A draw system) that wants to sort the entities by their Position.Z before rendering them. So it needs to operate a single collection of entities. I think I'm unable to achieve the z sorting i want while they are two separate queries.

    func NewTitleScene(c *core.Core) *TitleScene {
    	return &TitleScene{
    		Scene: core.Scene{
    			World: donburi.NewWorld(),
    			Systems: []core.SceneSystem{
    				system.NewAnimationSystem(c),
    				system.NewDrawSystem(c),
    				system.NewCursorSystem(c),
    			},
    		},
    	}
    }
    

    The DrawSystem :

    type DrawSystem struct {
    	query *query.Query
    }
    
    func NewDrawSystem(g *core.Core) *DrawSystem {
    	return &DrawSystem{
    		query: query.NewQuery(filter.Or(
    			filter.Contains(
    				component.Appearance,
    				component.Size,
    				component.Position,
    			),
    			filter.Contains(
    				component.Size,
    				component.Position,
    				component.Text,
    			),
    		)),
    	}
    }
    
    func (s *DrawSystem) Draw(c *core.Core, w *donburi.World, screen *ebiten.Image) {
    
    	lo.ForEach(
    		ZSortedEntries(*w, s.query),
    		func(entry *donburi.Entry, index int) {
    			appearance := component.GetAppearance(entry)
    			position := component.GetPosition(entry)
    			// TODO: 1️⃣ look into how to get text without panic
    			text := component.GetText(entry)
    			size := component.GetSize(entry)
    
    			if appearance.Image == nil {
    				return
    			}
    
    			// Draw frames
                            frame := *appearance.Frames[appearance.Frame]
                            frameImage := appearance.Image.SubImage(frame).(*ebiten.Image)
    			options := &ebiten.DrawImageOptions{}
    			options.GeoM.Translate(position.X, position.Y)
    			screen.DrawImage(frameImage, options)
    
    			if text == nil {
    				return
    			}
    
                            // do text rendering things.
                           ...
                   })
    }
    
    ...
    
    

    Problem is 1️⃣ , not all entities have text or appearance.

  • index out of range after removing entities from world

    index out of range after removing entities from world

    I'm having an issue removing entities from the world.

    I have a lifespan system that removes entities when they are "end of life". https://github.com/dfirebaugh/cheezewiz/blob/main/examples/choppa/internal/system/lifespan.go#L22-L41

    However, when I go to spawn more, i get an index out of range error:

    panic: runtime error: index out of range [5] with length 5
    
    goroutine 14 [running]:
    github.com/yohamta/donburi/internal/storage.(*SimpleStorage).Component(...)
            C:/Users/dfire/go/pkg/mod/github.com/yohamta/[email protected]/internal/storage/storage.go:37
    github.com/yohamta/donburi.(*Entry).Component(...)
            C:/Users/dfire/go/pkg/mod/github.com/yohamta/[email protected]/entry.go:33
    cheezewiz/examples/choppa/internal/entity.MakeProjectile({0x5b3e98, 0xc00012c700}, 0xc000010f90)
            C:/build/cheezewiz/examples/choppa/internal/entity/projectile.go:15 +0x5a5
    cheezewiz/examples/choppa/internal/system.Player.Update.func1(0xc00055a280?)
            C:/build/cheezewiz/examples/choppa/internal/system/player.go:54 +0xf3
    github.com/yohamta/donburi/query.(*Query).EachEntity.func1(0xc000004498?)
            C:/Users/dfire/go/pkg/mod/github.com/yohamta/[email protected]/query/query.go:42 +0x3b
    github.com/yohamta/donburi/query.(*Query).EachEntity(0xc0000c7c90?, {0x5b3e98, 0xc00012c700}, 0xc0000c7b80)
            C:/Users/dfire/go/pkg/mod/github.com/yohamta/[email protected]/query/query.go:47 +0x239
    cheezewiz/examples/choppa/internal/system.Player.Update({0x4?}, {0x5b3e98?, 0xc00012c700?})
            C:/build/cheezewiz/examples/choppa/internal/system/player.go:26 +0x4d
    cheezewiz/examples/choppa/internal/mediator.Mediator.Update(...)
            C:/build/cheezewiz/examples/choppa/internal/mediator/mediator.go:45
    cheezewiz/pkg/ebitenwrapper.(*Game).Update(0x0?)
            C:/build/cheezewiz/pkg/ebitenwrapper/ebitenwrapper.go:27 +0x22
    github.com/hajimehoshi/ebiten/v2.(*imageDumper).update(0xc00057e190)
            C:/Users/dfire/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/imagedumper_desktop.go:111 +0x52
    github.com/hajimehoshi/ebiten/v2.(*imageDumperGame).Update(0xc0000c7db8?)
            C:/Users/dfire/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/run.go:146 +0x7d
    github.com/hajimehoshi/ebiten/v2.(*gameForUI).Update(0x5b45a0?)
            C:/Users/dfire/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/gameforui.go:46 +0x22
    github.com/hajimehoshi/ebiten/v2/internal/ui.(*context).updateFrameImpl(0xc000068640, {0x5b45a0, 0x81dac0}, 0x1, 0x4090000000000000, 0x4090000000000000, 0xc0000c7e98?)
            C:/Users/dfire/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/ui/context.go:116 +0x157
    github.com/hajimehoshi/ebiten/v2/internal/ui.(*context).updateFrame(0xc000004630?, {0x5b45a0, 0x81dac0}, 0xc00006e238?, 0xc00041e820?, 0xc00041e8a8?)
            C:/Users/dfire/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/ui/context.go:64 +0x8e
    github.com/hajimehoshi/ebiten/v2/internal/ui.(*userInterfaceImpl).loop(0x81d8c0)
            C:/Users/dfire/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/ui/ui_glfw.go:1065 +0x1ea
    github.com/hajimehoshi/ebiten/v2/internal/ui.(*userInterfaceImpl).Run.func1()
            C:/Users/dfire/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/ui/run_notsinglethread.go:46 +0x13d
    created by github.com/hajimehoshi/ebiten/v2/internal/ui.(*userInterfaceImpl).Run
            C:/Users/dfire/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/ui/run_notsinglethread.go:33 +0x20a
    exit status 2```
  • Consider `system` feature

    Consider `system` feature

    The system as a "foreach-system" function feature in Bevy looks interesting. I am wondering if there's way to achieve the same in donburi using Generics. This way the user doesn't have to get components directly using query.EachEntity() but it will be suppilied to the system function automatically, which is nice.

  • Possible bug after SetComponent causing panic: runtime error: runtime error: index out of range [1] with length 1

    Possible bug after SetComponent causing panic: runtime error: runtime error: index out of range [1] with length 1

    Hello!

    I'm using the version github.com/yohamta/donburi v1.1.1

    I'm not sure if this is a bug, maybe I'm using the library incorrectly. I have the following scenario - I create an entity, then add another component to it.

    Then I repeat these steps again and at the stage of adding the second component I already get a panic: runtime error: index out of range [1] with length 1.

    Example of reproducing an error:

    https://gist.github.com/AlexeyDsov/78e2f499d96700f163bf87915cfa2b54

    Stack trace below:

    panic: runtime error: index out of range [1] with length 1
    
    goroutine 1 [running]:
    github.com/yohamta/donburi/internal/storage.(*Archetype).SwapRemove(...)
            .../go/pkg/mod/github.com/yohamta/[email protected]/internal/storage/archetype.go:39
    github.com/yohamta/donburi.(*world).TransferArchetype(0xc000110000?, 0xc000010280?, 0x2?, 0x2?)
            .../go/pkg/mod/github.com/yohamta/[email protected]/world.go:176 +0xb65
    github.com/yohamta/donburi.(*Entry).AddComponent(0xc0000660e0, 0xc00000c048, {0xc00007af68, 0x1, 0x52ec60?})
            .../go/pkg/mod/github.com/yohamta/[email protected]/entry.go:80 +0x166
    github.com/yohamta/donburi.Add[...](...)
            .../go/pkg/mod/github.com/yohamta/[email protected]/entry.go:26
    main.main()
            .../go/src/alexeydsov/test_world/main.go:28 +0x3d5
    
    
  • Add ComponentType.Get(World, *donburi.Entry) Method

    Add ComponentType.Get(World, *donburi.Entry) Method

    Add Generics method and eliminate the need for making tons of helper functions like this. Also, it will be Type-safe!

    func GetVelocity(entry *donburi.Entry) *VelocityData {
      return donburi.Get[VelocityData](entry, Velocity)
    }
    
    • [x] Add (c *ComponentType[T]) ComponentType.Get(World, *donburi.Entry) *T
    • [x] Add (c *ComponentType[T]) ComponentType.Set(World, *donburi.Entry, *T)
    • [x] Add (c *ComponentType[T]) ComponentType.SetValue(World, *donburi.Entry, T)
    • [x] Update README.md
  • Add Query() method to Component

    Add Query() method to Component

    Sometimes we want to query entities just for one component so having a default query instance in the component might be convenient. (e.g, for querying a singleton instance in a world such as GameState).

  • update ecs package

    update ecs package

    Simplify the API.

    Example:

    func NewGame() *Game {
    	// ...
    	g.ecs.
    		// Systems
    		AddSystem(system.NewSpawn().Update).
    		AddSystem(metrics.Update).
    		AddSystem(system.NewBounce(&g.bounds).Update).
    		AddSystem(system.Velocity.Update).
    		AddSystem(system.Gravity.Update).
    		// Renderers
    		AddRenderer(layers.LayerBackground, system.DrawBackground).
    		AddRenderer(layers.LayerMetrics, metrics.Draw).
    		AddRenderer(layers.LayerBunnies, system.Render.Draw)
    }
    
    func (g *Game) Update() error {
    	g.ecs.Update()
    	return nil
    }
    
    func (g *Game) Draw(screen *ebiten.Image) {
    	screen.Clear()
    	g.ecs.DrawLayer(layers.LayerBackground, screen)
    	g.ecs.DrawLayer(layers.LayerBunnies, screen)
    	g.ecs.DrawLayer(layers.LayerMetrics, screen)
    }
    
  • Create `features/dui` package

    Create `features/dui` package

    • [ ] Create dui package
    • [ ] Create Style component for layout options
    • [ ] Create a Flexbox layout engine based on ECS architecture (from scratch or migrate code from furex to cut corners)
    • [ ] Ability to add child entities
    dui.Create(ui.Config{
      Style: {
        // .. style options
        Top: 100,
        Left: 100,
        Width: 300,
        Height: 300,
      },
    }).WithChildren(
      ui.Create(ui.Config{
        Style: {
          // .. style options
        },
      },
    ))
    
  • Create `features/dtext` package

    Create `features/dtext` package

    Provide common functionality for text rendering.

    • [ ] Create a new features/dtext package
    • [ ] Create a Text component
    • [ ] Add a DrawText() function
    • [ ] Support text alignment (horizontal and vertical)
    • [ ] Support text wrapping
    • [ ] Support text color
    • [ ] Support text border color
  • Add `events` feature

    Add `events` feature

    Usage:

    type EnemyKilled struct {
    	EnemyID int
    }
    
    var EnemyKilledEvent = events.NewEventType(HandleEnemyKilled)
    
    func TestEvents(t *testing.T) {
    	w := donburi.NewWorld()
    
    	EnemyKilledEvent.Subscribe(w, HandleEnemyKilled)
    	EnemyKilledEvent.Publish(w, EnemyKilled{EnemyID: 1})
    
    	events.ProcessAllEvents(w)
    }
    
    func HandleEnemyKilled(w donburi.World, event EnemyKilled) {
    	// Process Event
    }
    
    
    
  • Add transform default value

    Add transform default value

    Add optional defaultValue for a component.

    - var Transform = donburi.NewComponentType[TransformData]()
    + var Transform = donburi.NewComponentType[TransformData](defaultValue)
    
  • Add `features/collision` package (using `resolv`)

    Add `features/collision` package (using `resolv`)

    Let's see how we can use SolarLune/resolv combining with ECS and the features/transform feature.

    • [ ] Create a new features/collision package using resolv
    • [ ] Create an example
  • Add `features/layers` package

    Add `features/layers` package

    Decouple the layering functionality from the ecs package.

    • [ ] Add features/layers package
    • [ ] Add a function func LayerTag(world World, layer LayerID) donburi.IComponentType
    • [ ] Add a function func Create(world World, components ...donburi.ComponentType) donburi.Entity
    • [ ] Add a function func CreateMany(world World, components ...donburi.ComponentType)
    • [ ] Add a function func AddTo(world World, layer LayerID, entry *donburi.Entry)
    • [ ] Add a function func NewQuery(layer LayerID, filter Filter) *query.Query
    • [ ] Update the ecs package to use the features/layer pckage
  • Add `Add()`, `Remove()` and `GetValue()` function to `ComponentType`

    Add `Add()`, `Remove()` and `GetValue()` function to `ComponentType`

    • [ ] Add (c *ComponentType[T]) ComponentType.Add(entry *donburi.Entry)
    • [ ] Add (c *ComponentType[T]) ComponentType.Remove(entry *donburi.Entry)
    • [ ] Add (c *ComponentType[T]) ComponentType.GetValue(entry *donburi.Entry) T
    • [ ] Update README.md
  • Deferred Entity Removal

    Deferred Entity Removal

    Add a safe mechanism to remove entities because accessing a removed entity would cause panic.

    • [ ] Create features/commands package
    • [ ] Create a queuing mechanism for processing commands
    • [ ] Create RemoveRecursive(world World, entry *donburi.Entry) function
    • [ ] Create ProcessCommands(world World) function
    • [ ] Update the ecs.ECS.Update() method to call ProcessCommands()
A simple game that I created with Ebiten game library as a way to teach myself Go. Enjoy!
A simple game that I created with Ebiten game library as a way to teach myself Go. Enjoy!

galactic-asteroid-belt Overview A simple game that I created with Ebiten game library as a way to teach myself Go. Enjoy! Run To run, you will need Go

Dec 2, 2021
Spaceshooter - A port to go of the pygame Space Shooter game using the ebiten library
Spaceshooter - A port to go of the pygame Space Shooter game using the ebiten library

Space Shooter This is a port to go of the pygame Space Shooter (https://github.c

Sep 29, 2022
Arkanoid game in Go using Ebiten game engine with ECS.
Arkanoid game in Go using Ebiten game engine with ECS.

Arkanoid-go Arkanoid game in Go using Ebiten game engine with ECS. You must have Git LFS installed when cloning the repository to download assets. See

Oct 9, 2022
Tetra3D is a 3D software renderer written in Go and Ebiten and made for games.
Tetra3D is a 3D software renderer written in Go and Ebiten and made for games.

Tetra3D Tetra3D Docs Support If you want to support development, feel free to check out my itch.io / Steam / Patreon. I also have a Discord server her

Dec 20, 2022
A simple breakout game made in Ebiten
A simple breakout game made in Ebiten

Ebiten Breakout Play online on itch.io A simple breakout game made in Ebiten. This is my first game made with Ebiten - a nice simple engine which allo

Nov 23, 2022
Minimal polymorphic solitaire engine in Go, ebiten

Gilbert Oddstream's Minimal Polymorphic Solitaire 5 There's a live WASM version here. Towards a polymorphic solitaire engine in Go+Ebiten, with help f

Dec 14, 2022
Implementation of a popular graphics benchmark written on Ebiten.
Implementation of a popular graphics benchmark written on Ebiten.

Ebiten Bunny Mark This is an implementation of the popular graphics benchmark written on Ebiten. The initial benchmark was created by Ian Lobb (code)

Dec 7, 2022
Simple 2D-grid game made with Ebiten
Simple 2D-grid game made with Ebiten

Simple 2D-grid game made with Ebiten

Mar 15, 2022
Utilities around dealing with images inside of game dev. Inspired by my hate for TGA.

Image Conversion Utilities around dealing with images inside of game dev. Inspired by my hate for TGA. Install go install ./cmd/imgconv Examples TGA

Oct 28, 2021
2D particle system simulator for Go
2D particle system simulator for Go

twodeeparticles An engine-agnostic 2D particle system simulator for Go twodeeparticles does not render particles itself. Any rendering engine can be u

Dec 6, 2022
Nintendo Entertainment System (NES) and Famicom emulator written in Go
Nintendo Entertainment System (NES) and Famicom emulator written in Go

go6502 Nintendo Entertainment System (NES) and Famicom emulator written in Go Dependencies go get -u github.com/go-gl/gl go get -u github.com/go-gl/gl

Apr 25, 2022
A dead simple 2D game library for Go
A dead simple 2D game library for Go

Ebiten (v2) A dead simple 2D game library for Go Ebiten is an open source game library for the Go programming language. Ebiten's simple API allows you

Dec 28, 2022
A hand-crafted 2D game library in Go
A hand-crafted 2D game library in Go

Pixel A hand-crafted 2D game library in Go. Take a look into the features to see what it can do. go get github.com/faiface/pixel If you are using Mod

Dec 31, 2022
Go bindings for raylib, a simple and easy-to-use library to enjoy videogames programming.
Go bindings for raylib, a simple and easy-to-use library to enjoy videogames programming.

raylib-go Golang bindings for raylib, a simple and easy-to-use library to enjoy videogames programming. Requirements Ubuntu X11 apt-get install libgl1

Dec 28, 2022
A simple Go library for 3D ray-tracing rendering, implementing the book Ray Tracing in One Weekend.
A simple Go library for 3D ray-tracing rendering, implementing the book Ray Tracing in One Weekend.

Ray Tracing in Go A Go implementation of the book Ray Tracing in One Weekend. The repository provides a library to describe and render your own scenes

Sep 23, 2022
build and animate objects according to verlet physics. pure golang library
build and animate objects according to verlet physics. pure golang library

verlet build and animate objects according to verlet physics. pure golang library Examples depend on pixel library, see requirements to build them: wo

Dec 22, 2022
Rock-paper-scissors game library
Rock-paper-scissors game library

jankensheep Rock-paper-scissors game library Examples Play with two players:

May 13, 2022
Golang writen 3D render library and some game-relative concepts

Golang writen 3D render library and some game-relative concepts.

Jun 6, 2022
Golang library for connecting to EOSIO SHIP

go-eosio-ship go-eosio-ship is a golang library built on top of go-eosio for con

Jan 3, 2022