Bug Description
When using the built-in Delay decorator from XML, the input port delay_msec is ignored.
- Expected behavior:
<Delay delay_msec="1500">…</Delay> returns RUNNING for ~1500 ms, then ticks its child and returns the child status.
- Actual behavior:
Delay completes immediately (effectively 0 ms) and the child is executed immediately, regardless of the delay_msec value.
This makes the Delay decorator unusable in XML trees (and any NodeConfig-based construction path).
How to reproduce
Option A (recommended): failing unit test (easy to verify)
Create a gtest that builds a tree from XML containing a Delay with delay_msec, then assert it stays RUNNING before the delay and only completes after the delay.
Build and run tests:
cmake -S . -B build
cmake --build build -j
ctest --test-dir build --output-on-failure
Option B: minimal XML (demonstrates end-user impact)
Use any application that loads BehaviorTree.CPP v4 XML and ticks it. A minimal tree is:
<root BTCPP_format="4">
<BehaviorTree ID="MainTree">
<Delay delay_msec="1500">
<AlwaysSuccess/>
</Delay>
</BehaviorTree>
</root>
Observed behavior: the tree finishes immediately instead of taking ~1.5 seconds.
Root cause analysis
In DelayNode::tick(), delay_msec is only read if read_parameter_from_ports_ is true:
|
NodeStatus DelayNode::tick() |
|
{ |
|
if(read_parameter_from_ports_) |
|
{ |
|
if(!getInput("delay_msec", msec_)) |
|
{ |
|
throw RuntimeError("Missing parameter [delay_msec] in DelayNode"); |
|
} |
|
} |
However, in the DelayNode(const std::string& name, const NodeConfig& config) constructor, read_parameter_from_ports_ is never enabled, and msec_ is initialized to 0:
|
DelayNode::DelayNode(const std::string& name, const NodeConfig& config) |
|
: DecoratorNode(name, config), timer_id_(0), msec_(0) |
|
{} |
Therefore, when created from XML / NodeConfig, msec_ stays at 0 and the timer is started with 0 ms.
For comparison, TimeoutNode uses the same read_parameter_from_ports_ gating logic:
|
NodeStatus TimeoutNode::tick() |
|
{ |
|
if(read_parameter_from_ports_) |
|
{ |
|
if(!getInput("msec", msec_)) |
|
{ |
|
throw RuntimeError("Missing parameter [msec] in TimeoutNode"); |
|
} |
|
} |
|
|
…but it correctly enables the port-reading behavior in its NodeConfig-based construction path (pattern that DelayNode should mirror).
Suggested fix
Enable port-reading in the NodeConfig-based constructor for DelayNode (i.e., ensure read_parameter_from_ports_ is set to true when constructed with a NodeConfig).
This makes <Delay delay_msec="…"> honor the configured delay.
Environment
- OS: Ubuntu 22.04
- Use case: XML trees (BTCPP format 4)
- Commit tested: 46fe516
Bug Description
When using the built-in
Delaydecorator from XML, the input portdelay_msecis ignored.<Delay delay_msec="1500">…</Delay>returnsRUNNINGfor ~1500 ms, then ticks its child and returns the child status.Delaycompletes immediately (effectively 0 ms) and the child is executed immediately, regardless of thedelay_msecvalue.This makes the
Delaydecorator unusable in XML trees (and anyNodeConfig-based construction path).How to reproduce
Option A (recommended): failing unit test (easy to verify)
Create a gtest that builds a tree from XML containing a
Delaywithdelay_msec, then assert it staysRUNNINGbefore the delay and only completes after the delay.Build and run tests:
cmake -S . -B build cmake --build build -j ctest --test-dir build --output-on-failureOption B: minimal XML (demonstrates end-user impact)
Use any application that loads BehaviorTree.CPP v4 XML and ticks it. A minimal tree is:
Observed behavior: the tree finishes immediately instead of taking ~1.5 seconds.
Root cause analysis
In
DelayNode::tick(),delay_msecis only read ifread_parameter_from_ports_is true:BehaviorTree.CPP/src/decorators/delay_node.cpp
Lines 25 to 33 in 46fe516
However, in the
DelayNode(const std::string& name, const NodeConfig& config)constructor,read_parameter_from_ports_is never enabled, andmsec_is initialized to 0:BehaviorTree.CPP/src/decorators/delay_node.cpp
Lines 14 to 16 in 46fe516
Therefore, when created from XML /
NodeConfig,msec_stays at 0 and the timer is started with 0 ms.For comparison,
TimeoutNodeuses the sameread_parameter_from_ports_gating logic:BehaviorTree.CPP/src/decorators/timeout_node.cpp
Lines 18 to 27 in 46fe516
…but it correctly enables the port-reading behavior in its
NodeConfig-based construction path (pattern thatDelayNodeshould mirror).Suggested fix
Enable port-reading in the
NodeConfig-based constructor forDelayNode(i.e., ensureread_parameter_from_ports_is set to true when constructed with aNodeConfig).This makes
<Delay delay_msec="…">honor the configured delay.Environment