BehaviorTree.CPP icon indicating copy to clipboard operation
BehaviorTree.CPP copied to clipboard

Pause and resume tree execution

Open Acwok opened this issue 5 years ago • 8 comments

Hi @facontidavide ,

Is there a way to pause and resume the execution of a tree, let's say from a keyboard input or something running in parallel ?

Acwok avatar Oct 28 '20 15:10 Acwok

I tried something with the following tree:

 <root >
     <BehaviorTree>
        <ReactiveSequence name="sequence">
            <Supervisor name="supervisor"/>
            <SequenceStar name="sequenceStar">
                <MyAsyncAction name="action_A"/>
                <MyAsyncAction name="action_B"/>
            </SequenceStar>
        </ReactiveSequence>
     </BehaviorTree>
 </root>

where:

  • Supervisor is a CoroActionNode which always returns SUCCESS except if the user hit p on the keyboard. Hitting p toggles between returning SUCCESS and returning RUNNING
  • MyAsyncAction is a CoroActionNode which returns SUCCESS after 5 seconds

With this setup, I can halt and resume the sequenceStar, but when it resumes, it always restarts from action_A even if it returned SUCCESS.
I was expecting that it would restart from action_B when I paused and resumed once action_A succeded.

Am I doing something wrong ?

Here is my code btw: src.zip

Acwok avatar Oct 29 '20 16:10 Acwok

Whenever action_B returns SUCCESS, the SequenceStar is restarted. Therefore there is no reason why what Supervisor does outside the SequenceStar should affect it.

facontidavide avatar Oct 30 '20 08:10 facontidavide

About the PAUSE/RESUME, I am actually planning to create an interface similar to "breakpoints" that are inserted and removed from the "outside". But For the time being, a solution for you would be to put your Supervisor inside the SequenceStar

But action_A and action_B would be still running sequentially.

What you need is to use ReactiveFallback instead of ReactiveSequence. Like this:

 <root >
     <BehaviorTree>
        <ReactiveFallback>
            <IsPaused name="check_pause"/>
            <SequenceStar name="sequenceStar">
                <MyAsyncAction name="action_A"/>
                <MyAsyncAction name="action_B"/>
            </SequenceStar>
        </ReactiveSequence>
     </BehaviorTree>
 </root>

Instead of Supervisor, IsPaused returns SUCCESS if in pause state, or FAILURE otherwise. The problem is that the sequenceStar will be halted....

facontidavide avatar Oct 30 '20 08:10 facontidavide

Whenever action_B returns SUCCESS, the SequenceStar is restarted. Therefore there is no reason why what Supervisor does outside the SequenceStar should affect it.

Actually, action_B is halted when Supervisor returns SUCCESS (first time I hit p). Here is my output: sequence

What does action_B return when it is halted ? Maybe if I found a way to make it returns FAILURE, it will work. I'm gonna try the ReactiveFallback too.

About the PAUSE/RESUME, I am actually planning to create an interface similar to "breakpoints" that are inserted and removed from the "outside".

I think it would be a nice feature, but it is more reassuring to be able to interrupt the execution at any time. That's why I was thinking about something like a Supervisor node which would allow several modes:

  • pause
  • resume
  • stop
  • step by step
  • inject an action or subtree

Acwok avatar Oct 30 '20 09:10 Acwok

Instead of Supervisor, IsPaused returns SUCCESS if in pause state, or FAILURE otherwise. The problem is that the sequenceStar will be halted....

Indeed :) fallback

Acwok avatar Oct 30 '20 09:10 Acwok

I managed to make the first tree work by commenting the following line in the lib: https://github.com/BehaviorTree/BehaviorTree.CPP/blob/f54f6d83e5c06f44eae2b4d9700f5178dbdb4159/src/controls/sequence_star_node.cpp#L77 Why is the index reset when halt is triggered ?

If B is halted, index is reset If B returns FAILURE, index is not reset (it is however reset then because of the ReactiveSequence)

If I comment this line, the SequenceStar correctly restart to the halted action.

Acwok avatar Oct 30 '20 16:10 Acwok

I have almost exactly the same needs and have been wanting to add pause / resume functionality. This is partly for debug purposes, for which @facontidavide 's breakpoint idea sound useful, but it is also a feature we need to implement our behaviors.

Imagine two behaviors, Docking and Error Recovery. We want to execute the Docking behavior unless we detect an Error in which case we want to perform Error Recovery and then resume Docking. Docking could be restarted, but sequences would have their current_child_idx_ reset and start ticking from the first child. In a well designed behavior tree the already completed nodes would detect that they completed successfully the first time and immediately return success.

Unfortunately that is not always possible, imagine picking up a cup, flipping it upside down to dump out the water, then flipping it back to upright and putting it down. The cup is in the same position, the only way to detect that the cup has been dumped out is to detect the water level or detect the water all over the floor. Detecting either of those is likely to be significantly more difficult than just resuming from where the tree was. In this hypothetical repeating the dumping of a cup might only waste time, but there can be actions that are dangerous to repeat.

Like @Acwok I toyed with creating a child class of sequence_start_node that removed the current_child_idx_ reset in halt, but we still want the ability of the node to be fully halted if necessary so I believe it needs a new function.

I propose to add a pause function to TreeNode; much like halt, this pause function only applies to async nodes in the RUNNING state. For most nodes pause will just call halt, but for sequence nodes (and any other where it makes sense) pause will not reset current_child_idx_.

I had originally hoped to not need to implement a new NodeStatus, but it seems like it must be added as (for example):

  • RUNNING seems like it would work, but for StatefulActionNode, onStart would not be recalled upon restart.
  • IDLE seems like a good choice too, but then onHalted would not be called if an ACTUAL halt occurs.

So it seems like a NodeStatus::PAUSED must be implemented that behaves almost identically to IDLE but allows the states to be differentiated.

EDIT: And I am happy to implement this change, I just want to discuss and make sure it's inline with what you'd want.

BenArtes avatar Feb 26 '21 18:02 BenArtes

We had the same need (to pause and resume) in out team, too. While @BenArtes 's idea was what I thought at first, we found that it might not be a good idea to put the controller inside the main BT. This decision is made because we are using ROS, and when making an ActionServer, adding pause wasn't an option.

What we tried was to implement a way to start the tree from a certain state. We made the following changes to the library:

  1. Add a pair of functions to export/import the current state in string, to every control node. (e.g. current_child_idx_ for sequence nodes)
  2. Add a logger to send the uid, NodeStatus change, and the state export string, if any.
  3. Add a function inside Tree, to halt and recursively set the NodeStatus and state import string.

Then, a controller (another program) that does the following:

  1. Records the log taken from the above mentioned logger.
  2. Halt the main BT program if needed.
  3. Restores by sending the list of uid, NodeStatus, and state export string to the main BT program.

This way, we can separate the controller element and the logic element without trying to implement a pause state into ROS's Actionlib.

While our work is wip and I don't think I can publish it, this idea might be another take for the pause problem, so I'm sharing my opinions.

SubaruArai avatar Jul 07 '21 00:07 SubaruArai

is there any updates on this issue? these features would be extremely helpful for debugging indeed

wisjaber avatar Apr 03 '23 10:04 wisjaber

Many of the problems and proposed solutions in this thread are based on different use cases.

  1. Groot 2 and BT.CPP 4.1.x finally implement the "breakpoint interface", which allow the user to interactively set breakpoints and wait for the user's input.

  2. If you want to skip a certain branch, based on a certain internal state, you should probably use the preconditions introduced in version 4.X. This is probably what I would have suggested to @BenArtes now.

  3. pausing the entire tree is as easy as stopping the tick of the root.

  4. The solution suggested by @SubaruArai seems very interesting, but it is more related to persistency (an interesting topic).

5 @wisesama , what do YOU have in mind?

I am closing this issue because the scope is now unclear. Feel free to open another one with a more specific scope

facontidavide avatar Apr 03 '23 14:04 facontidavide