Open futureneer opened 10 years ago
Also this is particularly an issue when the time scale of the tick is much smaller than the expected duration of the child of the sequence (or selector)
Also, by the way, I really like your implementation, extremely clean and easy to understand!
Good to hear you like the implementation since I feel I barely know what I am doing sometimes. :-)
I think you make a good point but as I understand behavior trees, we actually do want to tick every child of a selector or sequence on every pass through the tree. The reason is that events can occur in between ticks that could cause the running child to be no longer relevant or appropriate. For example, consider a sequence whose first child node is "Is there a dog chasing me?" and the second node is "Run away!". We want to know when the dog stops chasing us so we can stop running. So even when we are currently running away from a chasing dog, we still want to "tick" the first node to see if the dog is still chasing. Does that make sense? Or am I still missing something?
One way you can reduce the processing load is to put a time.sleep() statement inside the top level run() loop. The other idea I had (but haven't implemented) is to define a "rate" property for a task so that it only fires every 1/rate seconds. This way, for those nodes whose status is likely to change slowly (like checking the charge on a battery) they could run at a slower rate.
I should add that we only tick on the child nodes from "left to right" in a sequence until one of them returns either RUNNING or FAILURE (or SUCCESS for a selector). So any nodes still to the right of that node are not fired on during that pass through the tree.
I think I understand what you are saying. However, my assumption was always that in selectors and sequences, their children are atomic; that by definition they can only run one at a time. The situation you site above sounds like it would be more suited to a parallel node. For instance in your example, say that the "check dog" node is running, the tick would never hit "run away". Now say "check dog" succeeds... according to you the tick now reaches both "check dog" and "run away". Now assume that "run away" is running, and the tick returns FAILURE from "check dog". The entire sequence would fail, and nothing would stop "run away".
Now if the action "run away" was more like "take one step away" then that would make more sense, but if "run away" was something like a ROS action with a set goal like "run to the next house" then I think this wouldn't work. Nothing would preempt the action because the sequence would have returned FAILURE after "check dog".
Does that make sense? Or am I missing something now :)
So one other thought I had... in several papers I have read on BT's they also stipulate as you do that each child in the sequence is ticked (from left to right, as you said). However this dictates that the underlying actions will continue to return success once they have returned success the first time.
If we assume this is the psudo code for a sequence:
for i <- 1 to N do:
childStatus <- Tick(child(i))
if childStatus = running then
return running
if childStatus = failure then
return failure
return success
In this case, if the sequence ticks child(i) and child(i+1), then child(i) must have returned success again, and thus can't have ran again, unless it blocked everything (which I don't think it should be able to do).
Yes, I think we are on the same page and one of my use cases is a "patrol bot" that uses ROS actions to move between waypoints while checking the robot's battery level. You can see the script here.
My dog chasing example probably would have been better constructed like my patrol bot tree that looks like this:
Think of the patrol as the normal behavior of the person or robot. The root level BEHAVE node is a sequence. The STAY.HEALTHY task is a selector and takes higher priority than the PATROL task which is a sequence. The CHECK.BATTERY task is analogous to checking that there is no dog chasing us and recharging the battery is analogous to running away. If the CHECK.BATTERY condition fails (a dog is chasing us), then so does the STAY.HEALTHY selector task and the Behave sequence does not tick into the PATROL sequence. The STAY.HEALTHY selector then ticks into the RECHARGE node ("run away" for the dog example). In the ROS patrol bot situation, the recharge task calls up a new move_base task that first issues a "cancel_all_goals" before moving the robot to the docking station. For the dog chasing scenario, the "run away" task would cancel any on going behavior that uses the legs such as walking a patrol.
Does that get us any closer?
Oh, referring to your comment: "However this dictates that the underlying actions will continue to return success once they have returned success the first time." Yes, for my implementation of the SimpleActionTask in the pi_trees_ros library, I have a reset_after parameter that defaults to False. This means that if the action returns success, it will continue to do so until it is explicitly reset. One way to reset it is to put the sequence under a Loop task where the Loop task resets its children after each cycle. This is what I do with the Patrol Bot example.
Hi there, i've trying out pi_trees in the last days and i have a doubt that i think is related to this issue.
So first off a quick doubt on behaviour trees in general: I want to program a robot to get drink requests from people, fetch the drinks and deliver them. I should build the BT with the highest priority nodes on the left and less on the right correct? The objective is to serve X drinks, but to serve them i need to pick them up, and to pick them up i need request. (left to right / top to bottom).
--> COCKTAIL_PARTY --> CHECK_DELIVERED_DRINKS_t --> SERVE_DRINKS_sq --> CHECK_PENDING_DELIV_t --> NAV_LIVING_ROOM_t --> DELIVER_DRINK_t --> GET_DRINKS_sq --> CHECK_REQ_2_t --> NAV_KITCHEN_t --> FETCH_DRINK_t --> GET_REQUESTS_sq --> NAV_LIVING_ROOM_t --> GET_PERSON_REQ_t
Another thing i noticed is that when a state returns RUNNING, the tree will restart from the beggining, and taking for example the PATROL task you show in the previous comment, if the NAV2 returns RUNNING because it is moving to that point but has not yet reached it, when the new tick is initiated the NAV0 will take over and redirect the robot, even tough NAV2 just returned RUNNING and not SUCCESS. I've built the simple tree i've described before and i noticed this behaviour.
Could you explain where my reasoning is missing?
Hello--cool project! Yes, highest priority nodes on the left, lower on the right. To answer your question regarding the PATROL task, when NAV0 finishes, it stays in the state "SUCCESS" so even though each tick checks the status of NAV0, it will also get back SUCCESS and NAV0 will not fire again. You are correct that this won't always be the case; the NAV task here actually takes a non-trivial amount of time and is implemented by a ROS action using move_base. When the action is complete, we would like it to retain its status of SUCCESS. So the SimpleActionTask in the pi_trees library has a "reset_after" flag that defaults to False. For a different type of navigation task, like navigating to the robot's charging station, we would actually set this to True so that robot will return to the charging station again in the future when its battery runs low.
Hello!
I was looking at your code and I had a question, and this seemed to be the best place to ask. On your blog you talk about a "tick" propagating through the tree. How then does your implementation of sequence (and selector as well) behave in that case? Because you have a loop that runs each of the children every time the selectors run is called, even if a child node is running.
Shouldn't the selector keep track of which child is running and only tick that child? Then once it succeeds, the selector starts ticking the next child?
Thanks for the info! Cheers, Kel