Skip to content

Commit c603fd4

Browse files
committed
Fix Precondition to only check condition once
1 parent d4d8ae1 commit c603fd4

File tree

2 files changed

+118
-37
lines changed

2 files changed

+118
-37
lines changed

include/behaviortree_cpp/decorators/script_precondition.h

+13-9
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,23 @@ class PreconditionNode : public DecoratorNode
4848
throw RuntimeError("Missing parameter [else] in Precondition");
4949
}
5050

51+
// Only check the 'if' script if we haven't started ticking the children yet.
5152
Ast::Environment env = { config().blackboard, config().enums };
52-
if(_executor(env).cast<bool>())
53+
bool tick_children =
54+
_children_running || (_children_running = _executor(env).cast<bool>());
55+
56+
if(!tick_children)
5357
{
54-
auto const child_status = child_node_->executeTick();
55-
if(isStatusCompleted(child_status))
56-
{
57-
resetChild();
58-
}
59-
return child_status;
58+
return else_return;
6059
}
61-
else
60+
61+
auto const child_status = child_node_->executeTick();
62+
if(isStatusCompleted(child_status))
6263
{
63-
return else_return;
64+
resetChild();
65+
_children_running = false;
6466
}
67+
return child_status;
6568
}
6669

6770
void loadExecutor()
@@ -89,6 +92,7 @@ class PreconditionNode : public DecoratorNode
8992

9093
std::string _script;
9194
ScriptFunction _executor;
95+
bool _children_running = false;
9296
};
9397

9498
} // namespace BT

tests/gtest_preconditions.cpp

+105-28
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,111 @@ TEST(PreconditionsDecorator, StringEquals)
107107
ASSERT_EQ(counters[1], 1);
108108
}
109109

110+
class KeepRunning : public BT::StatefulActionNode
111+
{
112+
public:
113+
KeepRunning(const std::string& name, const BT::NodeConfig& config)
114+
: BT::StatefulActionNode(name, config)
115+
{}
116+
117+
static BT::PortsList providedPorts()
118+
{
119+
return {};
120+
}
121+
122+
BT::NodeStatus onStart() override
123+
{
124+
return BT::NodeStatus::RUNNING;
125+
}
126+
127+
BT::NodeStatus onRunning() override
128+
{
129+
return BT::NodeStatus::RUNNING;
130+
}
131+
132+
void onHalted() override
133+
{
134+
std::cout << "Node halted\n";
135+
}
136+
};
137+
138+
TEST(PreconditionsDecorator, ChecksConditionOnce)
139+
{
140+
BehaviorTreeFactory factory;
141+
factory.registerNodeType<KeepRunning>("KeepRunning");
142+
143+
const std::string xml_text = R"(
144+
145+
<root BTCPP_format="4" >
146+
<BehaviorTree ID="MainTree">
147+
<Sequence>
148+
<Script code = "A:=0" />
149+
<Script code = "B:=0" />
150+
<Precondition if=" A==0 " else="FAILURE">
151+
<KeepRunning _while="B==0" />
152+
</Precondition>
153+
</Sequence>
154+
</BehaviorTree>
155+
</root>)";
156+
157+
auto tree = factory.createTreeFromText(xml_text);
158+
159+
EXPECT_EQ(tree.tickOnce(), NodeStatus::RUNNING);
160+
// While the child is still running, attempt to fail the precondition.
161+
tree.rootBlackboard()->set("A", 1);
162+
EXPECT_EQ(tree.tickOnce(), NodeStatus::RUNNING);
163+
// Finish running the tree, the else condition should not be hit.
164+
tree.rootBlackboard()->set("B", 1);
165+
EXPECT_EQ(tree.tickOnce(), NodeStatus::SUCCESS);
166+
}
167+
168+
TEST(PreconditionsDecorator, CanRunChildrenMultipleTimes)
169+
{
170+
BehaviorTreeFactory factory;
171+
factory.registerNodeType<KeepRunning>("KeepRunning");
172+
std::array<int, 1> counters;
173+
RegisterTestTick(factory, "Test", counters);
174+
175+
const std::string xml_text = R"(
176+
177+
<root BTCPP_format="4" >
178+
<BehaviorTree ID="MainTree">
179+
<Sequence>
180+
<Script code = "A:=0" />
181+
<Script code = "B:=0" />
182+
<Script code = "C:=1" />
183+
<Repeat num_cycles="3">
184+
<Sequence>
185+
<Precondition if=" A==0 " else="SUCCESS">
186+
<TestA/>
187+
</Precondition>
188+
<KeepRunning _while="C==0" />
189+
<KeepRunning _while="B==0" />
190+
</Sequence>
191+
</Repeat>
192+
</Sequence>
193+
</BehaviorTree>
194+
</root>)";
195+
196+
auto tree = factory.createTreeFromText(xml_text);
197+
198+
EXPECT_EQ(tree.tickOnce(), NodeStatus::RUNNING);
199+
EXPECT_EQ(counters[0], 1); // Precondition hit once;
200+
201+
// In the second repeat, fail the precondition
202+
tree.rootBlackboard()->set("A", 1);
203+
tree.rootBlackboard()->set("B", 1);
204+
tree.rootBlackboard()->set("C", 0);
205+
EXPECT_EQ(tree.tickOnce(), NodeStatus::RUNNING);
206+
EXPECT_EQ(counters[0], 1); // Precondition still only hit once.
207+
208+
// Finally in the last repeat, hit the condition again.
209+
tree.rootBlackboard()->set("A", 0);
210+
tree.rootBlackboard()->set("C", 1);
211+
EXPECT_EQ(tree.tickOnce(), NodeStatus::SUCCESS);
212+
EXPECT_EQ(counters[0], 2); // Precondition hit twice now.
213+
}
214+
110215
TEST(Preconditions, Basic)
111216
{
112217
BehaviorTreeFactory factory;
@@ -246,34 +351,6 @@ TEST(Preconditions, Issue615_NoSkipWhenRunning_A)
246351
ASSERT_EQ(tree.tickOnce(), NodeStatus::RUNNING);
247352
}
248353

249-
class KeepRunning : public BT::StatefulActionNode
250-
{
251-
public:
252-
KeepRunning(const std::string& name, const BT::NodeConfig& config)
253-
: BT::StatefulActionNode(name, config)
254-
{}
255-
256-
static BT::PortsList providedPorts()
257-
{
258-
return {};
259-
}
260-
261-
BT::NodeStatus onStart() override
262-
{
263-
return BT::NodeStatus::RUNNING;
264-
}
265-
266-
BT::NodeStatus onRunning() override
267-
{
268-
return BT::NodeStatus::RUNNING;
269-
}
270-
271-
void onHalted() override
272-
{
273-
std::cout << "Node halted\n";
274-
}
275-
};
276-
277354
TEST(Preconditions, Issue615_NoSkipWhenRunning_B)
278355
{
279356
static constexpr auto xml_text = R"(

0 commit comments

Comments
 (0)