BehaviorTree.CPP
BehaviorTree.CPP copied to clipboard
confused about halt() method
I think I'm missing something fundamental about either behavior trees, or this implementation, I apologize.
I thought that the way behavior trees work is for some top-level controller to call tick() on the root node very frequently - in robotics at, say, 10Hz for instance, or faster even. Nowhere is this described or mentioned in any of the documentation, and so I wonder if I am misunderstanding that point.
I thought that behavior trees provided reactivity by means of this frequent re-ticking. Let's say there's just two nodes - a predicate "is door open", and some action node "walk through the door". We start off: the door is open! We then start walking through the door, but it takes a few seconds (the "walk through the door" node will return RUNNING over and over again, for a few seconds). But, because of the frequent re-ticking, the "is door open" condition might become false at any time (maybe someone else closed it or something). In that case, the tick never propagates to the "walk through the door" node at all (and the robot/player simply stops moving).
Is that understanding correct? In which case, why does the "halt()" method exist, and when exactly is it called, and by whom? Is it just a convenience, or is it necessary to interrupt a running node?
It would be really nice to see some more robotics-driven example, in which there is some reactive, interruptible action that takes a while. As it is, tbh I'm not really sure how I should implement, say a robotic movement that can be interrupted at any time. None of the example nodes return RUNNING, all of them look like they block in their implementations.
I feel like I'm just missing something obvious probably. Thx.
Hi @colinator The halt method is used with the asynchronous action execution to ensure that an action gets halted correctly if its execution is no longer needed (i.e., it is no longer ticked)
Think about a "walking" action for a bipedal robot. If the BT wants to stop the action and do something else, it should ensure that the walking action stopped "safely" (i.e., the robot has both feet on the ground).
Yeah, I get that bit. Is there any example of actually using this behavior tree library in a game or robot? I'm looking for some implementation that actually performs the high-frequency ticking. You might think this bit is trivial, and not worth explanation, but I don't think it is.
https://scope-robmosys.github.io/index in the "Toolchain" section there are different releases with different videos. The tick frequency was 10 Hz Is uses the halt function to stop navigation when needed (e.g. the battery gets low and the robot must stop and then move to the charging station)
This video is also a nice example https://www.youtube.com/watch?v=_EwNZG5Xo1k&ab_channel=RobMoSys
I believe I have the same confusion as @colinator.
Using the example provided by @miccol, seen in the video here: https://scope-robmosys.github.io/release2/. At 0:14, the robot is busy executing YARPAction - GoToDestination. While this action is continuously returning RUNNING, the battery level becomes low at 0:17. At this point, the YARPAction - GoToDestination is no longer ticked. But does it get halt()
'ed?
I believe yes. I see that in the example from the movie, everything is behind a 'ReactiveSequence' which halts all unticked children. Only because all branches of the tree are behind a ReactiveSequence, YARPAction - GoToDestination gets halt()
ed.
In our trees, we do not use a ReactiveSequence. Instead, we have a bunch of ControlNodes which choose left/right based on a boolean, all the way to the leaf nodes deciding which action gets executed. If a ControlNode decides to take a different path the next time it is ticked, the original path does not get halt()
ed. The problem with this is that our StatefulActionNode
s keep the state RUNNING instead of going back to IDLE. Maybe we should make the the ControlNodes stateful, and have them halt()
the other path whenever the returned state changes.
Should we overhaul all our trees to be behind a ReactiveSequence? Or is there something we missed?
Update/Edit:
I modified our custom implementation of boolean-type ControlNodes with 2 children to halt()
the other child. As I understand it now, all nodes are responsible for halt()
ing any children that require cleanup. With cleanup, I mean for example a child that was previously ticked and returning RUNNING, but is no longer being ticked. This child requires a cleanup in the form of a halt()
.
An example of our ControlNode: Suppose we have a robot that is playing soccer. We have a ControlNode named 'DoesOwnRobotHaveBall'. If it evaluates to True, it will executeTick()
the first child. If it evaluates to False, it will executeTick()
the second child. If it evaluates to True, the robot will start turning towards the goal while returning RUNNING. If the robot loses the ball, the ControlNode will suddenly start ticking the second child instead. When the ControlNode switches which child is ticked, the other child requires a halt()
.
This general rule / guideline for implementing custom nodes is missing in any of the documentation or code comments I have gone through.
The document says: "Another node might trigger a halt() signal". So how to trigger this function? I'm confused.
The document says: "Another node might trigger a halt() signal". So how to trigger this function? I'm confused.
Anyone? Please!
So how to trigger this function? I'm confused.
Using ReactiveSequence
or Parallel
or any other node that implement concurrent behaviors.
At the level of C++, halt() is not something you "do":
Action: "I want to halt someone or myself"
It is something that is done to you and you need to implement:
Action: "someone want to abort my operation. I should accomplish this desire".
@ekouters and others: keep in mind that only RUNNING Nodes receive the halt. If you are not in RUNNING state, you will not.
Usually, the halt() signal come from a ControlNodes
or DecoratorNodes
.
This is notably what Parallel
and ReactiveSequence
do: following some well-defined rules, they may halt the children in RUNNING state.
For instance, if you do:
<ReactiveSequence>
<CheckBattery/>
<MoveToLocation/>
</ReactiveSequence>
You are expressing the desire to send a halt signal to a RUNNING MoveToLocation
, as soon as CheckBattery
returns FAILURE.
This means that in the halt() method of MoveToLocation
, we must implement a routine that stop the robot and clean up its state.
A halted action doesn't return SUCCESS nor FAILURE. It doesn't return anything at all.
@ekouters
This general rule / guideline for implementing custom nodes is missing in any of the documentation or code comments I have gone through.
Isn't this what I explain in tutorial 4? https://www.behaviortree.dev/docs/tutorial-basics/tutorial_04_sequence
How can I improve that, in your opinion?
@facontidavide
This general rule / guideline for implementing custom nodes is missing in any of the documentation or code comments I have gone through.
Isn't this what I explain in tutorial 4? https://www.behaviortree.dev/docs/tutorial-basics/tutorial_04_sequence
That tutorial explains very well how to implement an asynchronous Action, and what an Action should do when it gets halt()
ed.
I was uncertain about when a ControlNode
should halt()
its children. Of course it depends on the implementation and intended behavior of your custom ControlNode
, but I could not find anything explaining what to expect from halt()
ing (or not halt()
ing) the node's children.
How can I improve that, in your opinion?
An example implementation of a custom ControlNode
is missing in the tutorials. That chapter could also discuss what effect tick()
ing and halt()
ing has on its children.
keep in mind that only RUNNING Nodes receive the halt. If you are not in RUNNING state, you will not.
This detail would also be very useful to mention in the chapter that discusses halt()
ing from the perspective of a ControlNode
.
was uncertain about when a ControlNode should halt() its children.
Each ControlNode will have its own logic.
halt() means "If you are in a RUNNING state, you must stop as fast as you can".
The only rule is: it is YOUR responsibility, when you implement a ControlNode, to think about all the transitions.
A ControlNode must handle:
- ALL the possible return values of a child.
- decide what to do with your RUNNING children if you receive a halt() or if you consider your operation "completed".
An advanced tutorial about creating a custom ControlNode would be certainly useful :smiley:
But honestly, my suggestion is to read the code or Sequence
and ReactiveSequence
and understand the difference.
Certainly you confused. It was because, c++ runtime, as well as c runtime, do not provide a safe mechanism to implement a safe task exiting. When a thread working is killed, it do not throw exceptions, do not execute finally, it directly become none. With a lot of handles still there. So the only way to safe implement a c/c++ typed robot os, the only way is to frequently check the termination flags. It was a hard work to do, specially, when a controller is all written by another person.
Finally, I suggest, the use of managed languages as implementation. At least they should support the following feature:
- Safe thread exit, an Exception can be caught and trigger the finally executed, at least at user call stacks(since some system call cannot be interruptible)
That was solely what we need to implement a safe exiting Tragically, the operating system is handling this, making things impossible. Yes, you should damn the OS
Current OS really need an improvement, at least allow safely thread exit at user space The other choice is to use C#/JAVA, which would automatically generate a different runtime, and the exception can be thrown in the runtime safely.
But I don't think the performance cost is worthy.