libgdx / gdx-ai

Artificial Intelligence framework for games based on libGDX or not. Features: Steering Behaviors, Formation Motion, Pathfinding, Behavior Trees and Finite State Machines
Apache License 2.0
1.18k stars 241 forks source link

Fix parallel task join orchestrator hard-resets its children. #134

Open metaphore opened 5 months ago

metaphore commented 5 months ago

The Parallel task's Join orchestrator calls reset() on its children when completed. That renders them unusable for any further use within the tree. Looks like it's a simple mistake of calling reset() (pool-related hard reset for task instances) instead of resetTask() (soft "restart" so the task can be run again).

Here's a simple example that demonstrates the bug.

public class BTreeParallelJoinTest extends ApplicationAdapter {

    private BehaviorTree bTree;

    @Override
    public void create() {
        super.create();

        bTree = new BehaviorTree(
                new Repeat(ConstantIntegerDistribution.NEGATIVE_ONE, // Repeat forever.
                        new Parallel(Parallel.Orchestrator.Join,
                                new AlwaysFail( // Any decorator will do here.
                                        new Success()
                                )
                        )
                )
        );
    }

    @Override
    public void render() {
        super.render();
        bTree.step();
    }
}

The Repeat task crashes on the second iteration at the AlwaysFail decorator as its child was nullified by the reset() call:

Exception in thread "main" java.lang.NullPointerException: Cannot read field "status" because "this.child" is null
    at com.badlogic.gdx.ai.btree.Decorator.run(Decorator.java:65)
    at com.badlogic.gdx.ai.btree.branch.Parallel$Orchestrator$2.execute(Parallel.java:215)
    at com.badlogic.gdx.ai.btree.branch.Parallel.run(Parallel.java:123)
    at com.badlogic.gdx.ai.btree.LoopDecorator.run(LoopDecorator.java:56)
    at com.badlogic.gdx.ai.btree.BehaviorTree.step(BehaviorTree.java:124)
    ...