beehive icon indicating copy to clipboard operation
beehive copied to clipboard

Resume behavior with selectors

Open ncblakely opened this issue 5 years ago • 2 comments

First off, thanks so much for this library! I'm new to behavior trees, but have successfully implemented this in my game and have found it to be very well designed and intuitive. It's a shame it hasn't gotten more attention.

Quick question about the intended behavior of the "resume" feature the TreeState object provides when dealing with a tree that has a Selector node. When I have no Selectors, resume works pretty much as I'd expect -- the next iteration always restarts at the last node that returned Status::RUNNING.

However, when I introduce a Selector into the picture, the resume behavior changes. Take this minimal repro of my situation:

struct TreeContext
{

};

#define TRACE_METHOD() printf("In %s\n", __FUNCTION__)

bool Leaf1(TreeContext& context)
{
    TRACE_METHOD();
    return true;
}

bool Leaf2(TreeContext& context)
{
    TRACE_METHOD();
    return true;
}

Status Leaf3(TreeContext& context)
{
    TRACE_METHOD();

    return Status::RUNNING;
}

Status Leaf4(TreeContext& context)
{
    TRACE_METHOD();
    return Status::SUCCESS;
}

bool Leaf5(TreeContext& context)
{
    TRACE_METHOD();
    return true;
}

bool Leaf6(TreeContext& context)
{
    TRACE_METHOD();
    return true;
}

bool Leaf7(TreeContext& context)
{
    TRACE_METHOD();
    return true;
}

int main()
{
    Builder<TreeContext> builder;

    Tree<TreeContext> tree = builder
        .selector()
            .sequence()
                .leaf(Leaf1)
                .leaf(Leaf2)
                .leaf(Leaf3)
                .leaf(Leaf4)
            .end()
            .sequence()
                .leaf(Leaf5)
                .leaf(Leaf6)
                .leaf(Leaf7)
            .end()
        .end()
        .build();

    TreeState state = tree.make_state();

    TreeContext context;
    while (true)
    {
        tree.process(state, context);
    }
}

With no Selector, one process() tick will stop at Leaf3, and the next will resume from there.

With the Selector however, it will start at the beginning every tick. Diving into the source code reveals that this is because the Selector node itself overwrites the resume index set by Leaf3 as the nodes are processed recursively.

Is this the intent? In my example, I wanted Leaf3 to be processed on the next iteration, bypassing the Selector. Intuitively, it seems to make sense for the Selector to only be re-processed when not resuming in the middle of a Sequence, but I could have entirely the wrong idea here as I am new to behavior trees. :)

Of course, I realize I could cause what I want to happen by adding conditions to Leaf1, Leaf2 that cause them to be effectively skipped in this scenario, but I am curious if this is a bug or intended behavior.

ncblakely avatar Dec 29 '20 08:12 ncblakely

~Thanks for your question. Sorry for the extremely late reply. I'm ashamed to admit you discovered a major flaw with the library. I wanted to avoid having the tree hold any state, but something needs to keep track of which node you're on between calls to process() for any of the pending states to work. Frankly, I didn't think anyone was interested so I never fixed it.~

cbush avatar Apr 13 '23 19:04 cbush

Wait, sorry, it's been a while... after reviewing some of this, I do see support for ~this~ the general resume case already built into the state object. There's a test for general resume functionality here: https://github.com/cbush/beehive/blob/main/test/tests/BeehiveTest.cpp#L130

We might be missing something, but it's perhaps not as bad as I thought... so yes, looks like there might be a bug in some of the specific node types...

cbush avatar Apr 13 '23 19:04 cbush