Skip to content

Commit 56115cb

Browse files
facontidavideclaude
andcommitted
Re-evaluate SUCCESS_IF and FAILURE_IF during RUNNING state (#917)
Previously, _successIf and _failureIf preconditions were only checked when the node was IDLE or SKIPPED. This meant that in ReactiveSequence, if a condition changed while an action was RUNNING, the change would not take effect until the action completed. Now these preconditions are also checked during RUNNING state, matching the existing behavior of _while. When a condition triggers, the node is halted and returns the appropriate status. Includes tests for both _successIf and _failureIf with async actions in ReactiveSequence that verify condition changes during RUNNING. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2c71b41 commit 56115cb

2 files changed

Lines changed: 105 additions & 4 deletions

File tree

‎src/tree_node.cpp‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,29 @@ Expected<NodeStatus> TreeNode::checkPreConditions()
222222
return NodeStatus::SKIPPED;
223223
}
224224
}
225-
else if(_p->status == NodeStatus::RUNNING && preID == PreCond::WHILE_TRUE)
225+
else if(_p->status == NodeStatus::RUNNING)
226226
{
227-
// what to do if the condition is false
228-
if(!parse_executor(env).cast<bool>())
227+
// Check WHILE_TRUE when running - halt if condition becomes false
228+
if(preID == PreCond::WHILE_TRUE)
229+
{
230+
// what to do if the condition is false
231+
if(!parse_executor(env).cast<bool>())
232+
{
233+
haltNode();
234+
return NodeStatus::SKIPPED;
235+
}
236+
}
237+
// Issue #917: Also check SUCCESS_IF and FAILURE_IF when running
238+
// This allows reactive sequences to respond to condition changes
239+
else if(preID == PreCond::SUCCESS_IF && parse_executor(env).cast<bool>())
229240
{
230241
haltNode();
231-
return NodeStatus::SKIPPED;
242+
return NodeStatus::SUCCESS;
243+
}
244+
else if(preID == PreCond::FAILURE_IF && parse_executor(env).cast<bool>())
245+
{
246+
haltNode();
247+
return NodeStatus::FAILURE;
232248
}
233249
}
234250
}

‎tests/gtest_preconditions.cpp‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,3 +508,88 @@ TEST(Preconditions, SkippedSequence)
508508
status = tree.tickWhileRunning();
509509
ASSERT_EQ(status, BT::NodeStatus::SUCCESS);
510510
}
511+
512+
// Test for Issue #917: _successIf and _failureIf not re-evaluated when node is RUNNING
513+
// in a ReactiveSequence
514+
TEST(Preconditions, Issue917_SuccessIfWhenRunning)
515+
{
516+
// The issue is that _successIf is only checked when a node is IDLE or SKIPPED.
517+
// In a ReactiveSequence, when a variable changes to make _successIf true,
518+
// the running node should return SUCCESS (after being halted).
519+
520+
BehaviorTreeFactory factory;
521+
factory.registerNodeType<KeepRunning>("KeepRunning");
522+
523+
static constexpr auto xml_text = R"(
524+
<root BTCPP_format="4">
525+
<BehaviorTree ID="Main">
526+
<ReactiveSequence>
527+
<Script code="loop := loop + 1; my_var := (loop >= 3) ? 42 : 0"/>
528+
<KeepRunning _successIf="my_var != 0"/>
529+
</ReactiveSequence>
530+
</BehaviorTree>
531+
</root>
532+
)";
533+
534+
auto tree = factory.createTreeFromText(xml_text);
535+
tree.rootBlackboard()->set("loop", 0);
536+
tree.rootBlackboard()->set("my_var", 0);
537+
538+
// First tick: loop=1, my_var=0, KeepRunning returns RUNNING
539+
auto status = tree.tickOnce();
540+
ASSERT_EQ(status, NodeStatus::RUNNING);
541+
ASSERT_EQ(tree.rootBlackboard()->get<int>("loop"), 1);
542+
ASSERT_EQ(tree.rootBlackboard()->get<int>("my_var"), 0);
543+
544+
// Second tick: loop=2, my_var=0, KeepRunning still RUNNING
545+
status = tree.tickOnce();
546+
ASSERT_EQ(status, NodeStatus::RUNNING);
547+
ASSERT_EQ(tree.rootBlackboard()->get<int>("loop"), 2);
548+
ASSERT_EQ(tree.rootBlackboard()->get<int>("my_var"), 0);
549+
550+
// Third tick: loop=3, my_var=42, _successIf should trigger SUCCESS
551+
status = tree.tickOnce();
552+
ASSERT_EQ(tree.rootBlackboard()->get<int>("loop"), 3);
553+
ASSERT_EQ(tree.rootBlackboard()->get<int>("my_var"), 42);
554+
// This is the critical assertion - the node should return SUCCESS
555+
// because _successIf="my_var != 0" is now true
556+
ASSERT_EQ(status, NodeStatus::SUCCESS);
557+
}
558+
559+
TEST(Preconditions, Issue917_FailureIfWhenRunning)
560+
{
561+
// Similar test for _failureIf: verify it's also evaluated when RUNNING
562+
563+
BehaviorTreeFactory factory;
564+
factory.registerNodeType<KeepRunning>("KeepRunning");
565+
566+
static constexpr auto xml_text = R"(
567+
<root BTCPP_format="4">
568+
<BehaviorTree ID="Main">
569+
<ReactiveSequence>
570+
<Script code="loop := loop + 1; my_var := (loop >= 3) ? 42 : 0"/>
571+
<KeepRunning _failureIf="my_var != 0"/>
572+
</ReactiveSequence>
573+
</BehaviorTree>
574+
</root>
575+
)";
576+
577+
auto tree = factory.createTreeFromText(xml_text);
578+
tree.rootBlackboard()->set("loop", 0);
579+
tree.rootBlackboard()->set("my_var", 0);
580+
581+
// First tick: loop=1, my_var=0, KeepRunning returns RUNNING
582+
auto status = tree.tickOnce();
583+
ASSERT_EQ(status, NodeStatus::RUNNING);
584+
585+
// Second tick: loop=2, my_var=0, KeepRunning still RUNNING
586+
status = tree.tickOnce();
587+
ASSERT_EQ(status, NodeStatus::RUNNING);
588+
589+
// Third tick: loop=3, my_var=42, _failureIf should trigger FAILURE
590+
status = tree.tickOnce();
591+
ASSERT_EQ(tree.rootBlackboard()->get<int>("my_var"), 42);
592+
// This is the critical assertion - the node should return FAILURE
593+
// because _failureIf="my_var != 0" is now true
594+
ASSERT_EQ(status, NodeStatus::FAILURE);
595+
}

0 commit comments

Comments
 (0)