Skip to content

Query callback is called twice for the same entity #119

@mrombout

Description

@mrombout

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions