yohamta / donburi Goto Github PK
View Code? Open in Web Editor NEWJust another ECS library for Go/Ebitengine
Home Page: https://pkg.go.dev/github.com/yohamta/donburi
License: Other
Just another ECS library for Go/Ebitengine
Home Page: https://pkg.go.dev/github.com/yohamta/donburi
License: Other
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
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```
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.
Add a safe mechanism to remove entities because accessing a removed entity would cause panic
.
features/commands
packageRemoveRecursive(world World, entry *donburi.Entry)
functionProcessCommands(world World)
functionecs.ECS.Update()
method to call ProcessCommands()
panic: runtime error: index out of range [-5]
goroutine 69 [running]:
github.com/yohamta/donburi/internal/storage.(*Archetype).SwapRemove(...)
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
when the archetype exists that matches the query but there's no live entity, the Query.FirstEntity() crashes with panic: runtime error: slice bounds out of range
Provide common functionality for text rendering.
features/dtext
packageText
componentDrawText()
functionLet's see how we can use SolarLune/tetra3d with ECS.
aabb is simple but fast.
features/aabb
packageaabb.Collider
componentIf you try to publish an Event that no handler is listening the program panic since no event bus is found.
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})
}
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.
For Intel macOS compatibility, it is necessary to update the go.mod dependencies to Go v1.22.0 and Ebitengine v2.6.6.
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 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)
}
(c *ComponentType[T]) ComponentType.Get(World, *donburi.Entry) *T
(c *ComponentType[T]) ComponentType.Set(World, *donburi.Entry, *T)
(c *ComponentType[T]) ComponentType.SetValue(World, *donburi.Entry, T)
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?
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}
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
.
I would expect ROUND 1
and ROUND 2
to behave the same because the component is added and removed in each round.
Let's see how we can use SolarLune/resolv combining with ECS and the features/transform
feature.
features/collision
package using resolv
features/sprite
packageSprite
componentChange the function to just constants:
const (
ToDegrees = 180 / math.Pi
ToRadians = math.Pi / 180
)
Hello! Please feel free to drop a comment here if you have any interesting feature or improvement ideas for donburi. Thanks.
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.
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).
Decouple the layering functionality from the ecs
package.
features/layers
packagefunc LayerTag(world World, layer LayerID) donburi.IComponentType
func Create(world World, components ...donburi.ComponentType)
donburi.Entityfunc CreateMany(world World, components ...donburi.ComponentType)
func AddTo(world World, layer LayerID, entry *donburi.Entry)
func NewQuery(layer LayerID, filter Filter) *query.Query
ecs
package to use the features/layer
pckageIt would be useful to have a Queue inside the world for communication between systems. It can be type-safe using Generics.
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```
I create a game using the library, is very simple and not that fun. If you find it worth, please to the projects list.
Game page: https://kam1sama.itch.io/goingo
Source Code: https://github.com/joelschutz/goingo
dui
packageStyle
component for layout optionsdui.Create(ui.Config{
Style: {
// .. style options
Top: 100,
Left: 100,
Width: 300,
Height: 300,
},
}).WithChildren(
ui.Create(ui.Config{
Style: {
// .. style options
},
},
))
(c *ComponentType[T]) ComponentType.Add(entry *donburi.Entry)
(c *ComponentType[T]) ComponentType.Remove(entry *donburi.Entry)
(c *ComponentType[T]) ComponentType.GetValue(entry *donburi.Entry) T
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.