Coder Social home page Coder Social logo

yohamta / donburi Goto Github PK

View Code? Open in Web Editor NEW
222.0 4.0 21.0 1.97 MB

Just another ECS library for Go/Ebitengine

Home Page: https://pkg.go.dev/github.com/yohamta/donburi

License: Other

Go 100.00%
ebitengine ecs gamedev entity-component-system game-engine

donburi's People

Contributors

airtonix avatar badcorporatelogo avatar bradbeam avatar imthatgin avatar infiniteyak avatar joaowiciuk avatar joelschutz avatar korve avatar m110 avatar pkuebler avatar setanarut avatar soockee avatar soul-walker avatar yohamta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

donburi's Issues

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

Refactoring to v3

  • Refactor core logic of flex-layout algorithm
  • Refactor to be more idiomatic structure in go (avoid unnecessary embedding structs)
  • Refactor the API

Headless/compile flag to disable rendering features

I have a single go module which contains both client and server code. I want to be able to use the ECS, without necessarily running it in a window.

Therefore, I'd like to request the addition of a compile flag to disable the ebiten dependency if possible.

I get something like this, because the server does not have a display:

panic: NotInitialized: The GLFW library is not initialized

goroutine 1 [running]:
github.com/hajimehoshi/ebiten/v2/internal/cglfw.acceptError({0x0, 0x0, 0x10004?})
        /config/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/cglfw/error.go:184 +0x23c
github.com/hajimehoshi/ebiten/v2/internal/cglfw.panicError(...)
        /config/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/cglfw/error.go:195
github.com/hajimehoshi/ebiten/v2/internal/cglfw.WindowHint(0x0?, 0x0?)
        /config/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/cglfw/window.go:317 +0x34
github.com/hajimehoshi/ebiten/v2/internal/glfw.WindowHint(...)
        /config/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/glfw/glfw_cglfw.go:347
github.com/hajimehoshi/ebiten/v2/internal/ui.initialize()
        /config/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/ui/ui_glfw.go:174 +0x36
github.com/hajimehoshi/ebiten/v2/internal/ui.init.2()
        /config/go/pkg/mod/github.com/hajimehoshi/ebiten/[email protected]/internal/ui/ui_glfw.go:159 +0x13
exit status 2```

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.

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()

Error on Transferring Archetype

panic: runtime error: index out of range [-5]

goroutine 69 [running]:
github.com/yohamta/donburi/internal/storage.(*Archetype).SwapRemove(...)

Getting a list of entity's components

I'm trying to build a world state packet for server-client ECS, and need to build a list of entities with their component data, and I'm looking for a way to do that, but donburi does not seem to have an API I can use to query arbitrary component data, or just to serialize an entity

TODO

  • tests: add more tests
  • tests: testcov
  • chore: automated test with GitHub Action
  • chore: Update ebitengin version

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 `features/aabb` package

aabb is simple but fast.

  • Add features/aabb package
  • Add aabb.Collider component
  • Support HitBox configuration
  • Support Simple collision detection (AABB)
  • Support rotations
  • Support quadtree

Panic on event Publishing

If you try to publish an Event that no handler is listening the program panic since no event bus is found.

How to Reproduce

Create a new event and try to publish to it.

type EnemyKilled struct {
	EnemyID int
}

var EnemyKilledEvent = events.NewEventType[*EnemyKilled]()

func TestEvents(t *testing.T) {
	w := donburi.NewWorld()
	EnemyKilledEvent.Publish(w, &EnemyKilled{EnemyID: 1})
}

Problem

It makes sense to only initialize a bus if there is a handler to react to it, but in a game you often need to add or remove subscribers dymanically. Attaching the bus initialization to the Subscribe method makes it difficult since all the system depends on that first subscription.

Outdated Go/Ebitengine version.

For Intel macOS compatibility, it is necessary to update the go.mod dependencies to Go v1.22.0 and Ebitengine v2.6.6.


Ebitengine Release Notes:

v2.6.6
Bug Fixes
Issues for v2.6.6

Fixed the issue where applications did not launch when built with Go 1.22 on Intel macOS (#2908).

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)
}
  • Add (c *ComponentType[T]) ComponentType.Get(World, *donburi.Entry) *T
  • Add (c *ComponentType[T]) ComponentType.Set(World, *donburi.Entry, *T)
  • Add (c *ComponentType[T]) ComponentType.SetValue(World, *donburi.Entry, T)
  • Update README.md

Query callback is called twice for the same entity

It appears that sometimes the callback for a query is called twice when dynamically adding and removing components from an entity. Is it perhaps a bug, or caused by misuse of the API?

Self-contained example

The self-contained example below hopefully demonstrates the problem. I've also put the example on Go Playground.

package main

import (
	"log"

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

type PositionData struct {
	X int
	Y int
}

var Position = donburi.NewComponentType[PositionData]()

var Player = donburi.NewTag()

type DoActionData struct {
	action string
}

var DoAction = donburi.NewComponentType[DoActionData]()

func main() {
	world := donburi.NewWorld()

	playerEntity := world.Create(Player, Position)

	log.Println("=== ROUND 1")
	receivePlayerInput(world)
	processActions(world, playerEntity)
	log.Println("=== ROUND 2")
	receivePlayerInput(world)
	processActions(world, playerEntity)
}

func receivePlayerInput(world donburi.World) {
	log.Println("receivePlayerInput")

	query := donburi.NewQuery(filter.Contains(Player, Position))
	log.Printf("receivePlayerInput query count: %d\n", query.Count(world))
	query.Each(world, func(e *donburi.Entry) {
		log.Printf("receivePlayerInput query callback for %s\n", e)
		log.Printf("adding DoAction to %s\n", e)
		donburi.Add(e, DoAction, &DoActionData{
			action: "do thing",
		})
	})
}

func processActions(world donburi.World, playerEntity donburi.Entity) {
	playerEntry := world.Entry(playerEntity)
	log.Printf("removing DoAction from %s\n", playerEntry)
	donburi.Remove[DoActionData](playerEntry, DoAction)
}

Output

2024/01/23 00:03:30 === ROUND 1
2024/01/23 00:03:30 receivePlayerInput
2024/01/23 00:03:30 receivePlayerInput query count: 1
2024/01/23 00:03:30 receivePlayerInput query callback for Entry: {Entity: {id: 1, version: 0}, Layout: {, PositionData}, Valid: true}
2024/01/23 00:03:30 adding DoAction to Entry: {Entity: {id: 1, version: 0}, Layout: {, PositionData}, Valid: true}
2024/01/23 00:03:30 removing DoAction from Entry: {Entity: {id: 1, version: 0}, Layout: {, PositionData, DoActionData}, Valid: true}
2024/01/23 00:03:30 === ROUND 2
2024/01/23 00:03:30 receivePlayerInput
2024/01/23 00:03:30 receivePlayerInput query count: 1
2024/01/23 00:03:30 receivePlayerInput query callback for Entry: {Entity: {id: 1, version: 0}, Layout: {, PositionData}, Valid: true}
2024/01/23 00:03:30 adding DoAction to Entry: {Entity: {id: 1, version: 0}, Layout: {, PositionData}, Valid: true}
2024/01/23 00:03:30 receivePlayerInput query callback for Entry: {Entity: {id: 1, version: 0}, Layout: {, PositionData, DoActionData}, Valid: true}
2024/01/23 00:03:30 adding DoAction to Entry: {Entity: {id: 1, version: 0}, Layout: {, PositionData, DoActionData}, Valid: true}
2024/01/23 00:03:30 removing DoAction from Entry: {Entity: {id: 1, version: 0}, Layout: {, PositionData, DoActionData}, Valid: true}

Actual behavior

In ROUND 1 the query behaves as I would expect. There is 1 entity that has both the Player and Position components, the callback is called once and the DoAction component is added. It is then processed and the DoAction component is removed from the entity.

In ROUND 2 the entity should be back in the state it was before with only the Player and Position components. The query .Count() returns 1, which is correct. But the query now appears to call the callback twice for the same entity, but with different layouts. Once without the DoAction and once without the DoAction.

Expected behavior

I would expect ROUND 1 and ROUND 2 to behave the same because the component is added and removed in each round.

Improvement / Feature Ideas

Hello! Please feel free to drop a comment here if you have any interesting feature or improvement ideas for donburi. Thanks.

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.

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).

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

Type-safe Queue

It would be useful to have a Queue inside the world for communication between systems. It can be type-safe using Generics.

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```

Strong slowdown in the recent versions

Problem

Hello, I found a very strange behavior. Looks like the version of lib that is a year older works better than the current one...

Branch develop:
image

Branch main:
image

Also, latest tag has broken wheel in bunnymark example.

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
    },
  },
))

Adding Features

  • Add Event Listener function for Component addition / removal
  • Iteration over all entities
  • Deletion of all entities
  • Allow struct for System not only function

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.