Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions tests/gtest_parallel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,3 +591,106 @@ TEST(Parallel, PauseWithRetry)
// the whole process should take about 300 milliseconds
ASSERT_LE(toMsec(t2 - t1) - 300, margin_msec * 2);
}

// Issue #819: Demonstrates that Sequence does NOT re-evaluate conditions
// while a sibling action is RUNNING, whereas ReactiveSequence DOES.
// This is expected behavior, not a bug.
TEST(Parallel, Issue819_SequenceVsReactiveSequence)
{
using namespace BT;

// Test 1: Regular Sequence - condition NOT re-evaluated
{
static const char* xml_text = R"(
<root BTCPP_format="4">
<BehaviorTree ID="TestTree">
<Parallel success_count="2" failure_count="1">
<Sequence>
<TestCondition name="cond1"/>
<Sleep msec="200"/>
</Sequence>
<Sequence>
<TestCondition name="cond2"/>
<Sleep msec="200"/>
</Sequence>
</Parallel>
</BehaviorTree>
</root>
)";
BehaviorTreeFactory factory;
std::array<int, 2> tick_counts = { 0, 0 };

// Register conditions that count their ticks
factory.registerSimpleCondition("TestCondition", [&](TreeNode& node) {
const std::string& name = node.name();
if(name == "cond1")
tick_counts[0]++;
else if(name == "cond2")
tick_counts[1]++;
return NodeStatus::SUCCESS;
});

auto tree = factory.createTreeFromText(xml_text);

// First tick: both conditions evaluated
auto status = tree.tickExactlyOnce();
ASSERT_EQ(NodeStatus::RUNNING, status);
ASSERT_EQ(1, tick_counts[0]); // cond1 ticked once
ASSERT_EQ(1, tick_counts[1]); // cond2 ticked once

// Second tick: conditions should NOT be re-evaluated (Sequence behavior)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
status = tree.tickExactlyOnce();
ASSERT_EQ(NodeStatus::RUNNING, status);
// Conditions are NOT re-ticked because Sequence remembers current_child_idx_
ASSERT_EQ(1, tick_counts[0]); // Still 1 - NOT re-evaluated
ASSERT_EQ(1, tick_counts[1]); // Still 1 - NOT re-evaluated
}

// Test 2: ReactiveSequence - condition IS re-evaluated every tick
{
static const char* xml_text = R"(
<root BTCPP_format="4">
<BehaviorTree ID="TestTree">
<Parallel success_count="2" failure_count="1">
<ReactiveSequence>
<TestCondition name="cond1"/>
<Sleep msec="200"/>
</ReactiveSequence>
<ReactiveSequence>
<TestCondition name="cond2"/>
<Sleep msec="200"/>
</ReactiveSequence>
</Parallel>
</BehaviorTree>
</root>
)";
BehaviorTreeFactory factory;
std::array<int, 2> tick_counts = { 0, 0 };

factory.registerSimpleCondition("TestCondition", [&](TreeNode& node) {
const std::string& name = node.name();
if(name == "cond1")
tick_counts[0]++;
else if(name == "cond2")
tick_counts[1]++;
return NodeStatus::SUCCESS;
});

auto tree = factory.createTreeFromText(xml_text);

// First tick
auto status = tree.tickExactlyOnce();
ASSERT_EQ(NodeStatus::RUNNING, status);
ASSERT_EQ(1, tick_counts[0]);
ASSERT_EQ(1, tick_counts[1]);

// Second tick: conditions SHOULD be re-evaluated (ReactiveSequence behavior)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
status = tree.tickExactlyOnce();
ASSERT_EQ(NodeStatus::RUNNING, status);
// Conditions ARE re-ticked because ReactiveSequence always starts from index 0
ASSERT_EQ(2, tick_counts[0]); // Re-evaluated!
ASSERT_EQ(2, tick_counts[1]); // Re-evaluated!
}
}
Loading