-
Notifications
You must be signed in to change notification settings - Fork 151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[decorators] finally-style decorators and idioms #427
base: devel
Are you sure you want to change the base?
Conversation
163ab1a
to
812686b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for starting this so promptly!
I like the idea of a finally
decorator to enforce that ticking the child happens in terminate
rather than update
. Although this in isolation isn't the finally
functionality; as your comment mentions, it should be used within a Parallel to achieve that.
What do you think of renaming this decorator to something like TickOnTerminatation
, and then having a finally
idiom that takes in two children -- a main behavior and the cleanup behavior -- and puts them together in a parallel, with the TickOnTermination
decorator on top of the cleanup behavior?
) | ||
) | ||
if new_status == common.Status.INVALID: | ||
self.decorated.tick_once() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is a tick_once
necessarily enough to process the child behavior's sequence? The child behavior might be making an asynchronous call (e.g., to a ROS service, as I contributed in this py_trees_ros
PR). I think this should instead tick the child behavior to completion.
(Which brings up a question of how often to tick the child behavior -- perhaps that should be a parameter passed to __init__
?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not at all, you can see in my comments to the class a single tick won't suffice for all cases (i.e. here.
I think what we need then is a renamed decorator and two idioms. One for a single-tick finally with the decorator and one for a more complex multi-tick finally as highlighted in that comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I missed that part of the comment.
Adding onto what you wrote, and in light of our discussion on cloning, the multi-tick idiom would need three children passed in: the main behavior and two copies of the finally behavior, one to run on success and one to run on failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still trying to think of how we can eliminate the need to clone behaviors. What if our idiom is as follows:
Sequence
| FailureIsSuccess
| | StatusToBlackboard
| | | Work
| Finally
| BlackboardToStatus
This way, if the work fails, the failure is passed up, and if finally fails, the failure is passed up. The only way this tree succeeds if both the Work
and Finally
succeeds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Which brings up a question of how often to tick the child behavior -- perhaps that should be a parameter passed to init?)
With respect to this, I think the most intuitive default should be to tick as often as it needs to go to completion, i.e. to SUCCESS || FAILURE. If for some reason you explicitly need to control the # of ticks, you can use a Counter and a Parallel in the subtree to control that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I wasn't thinking about # of ticks, I was thinking about ticking rate. I agree with you that we should tick as necessary to complete the tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still trying to think of how we can eliminate the need to clone behaviors. What if our idiom is as follows:
Sequence
| FailureIsSuccess
| | StatusToBlackboard
| | | Work
| Finally
| BlackboardToStatus
After using them quite heavily, I found I am not a fan of the FailureIsSuccess
style decorators and almost never use them now. They make it awfully hard to inspect a tree - human brains don't tend to fare so well with flip-flops, negations and even worse, double negations. A larger-tree is far more acceptable than a hard-to-read tree, especially if you make use of viz tooling that collapses parts of the tree.
Also, I think we should indeed pass in two handler behaviours (or subtrees). That makes it wonderfully general - you can pass in different handlers for failure/success or a clone of them.
812686b
to
b008e6e
Compare
Updated the PR with an Favouring the word eventually here, because:
|
b008e6e
to
15d3060
Compare
This all sounds good :) Fwiw, the multi-tick idiom you proposed is fundamentally a |
76d2885
to
5aa8d22
Compare
Alright, about done I think.
I did think about switching the idiom names to I have paid homage to python's I'll leave this PR open for a bit, feel free to comment on it further. Thanks for being a useful sounding board! |
a5a6c0b
to
8abb2cf
Compare
8abb2cf
to
bcee752
Compare
children=[on_failure, behaviours.Failure(name="Failure")], | ||
) | ||
subtree_root = composites.Selector( | ||
name=name, memory=False, children=[on_success_sequence, on_failure_sequence] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this have memory? Once on_success_sequence
fails, you no longer want to run it again; you only want to run on_failure_sequence
till completion.
assert blackboard.flag | ||
|
||
|
||
def test_eventually_swiss() -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the issue with memory=False
doesn't arise in this test because the on_failure
and on_success
behaviors resolve in a single tick.
Returns: | ||
the root behaviour | ||
""" | ||
set_result_true = py_trees.behaviours.SetBlackboardVariable( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is the best example of eventually_swiss
because the on_failure
and on_success
each only take one tick. In fact, this example is basically equivalent to your eventually
example above. Perhaps on_success
and on_failure
each be a sequence with a tick counter, to better illustrate the multi-tick nature of this idiom?
Other than the above comments, looks good :) |
Thinking about this more, I realized that the multi-tick
I think to address this, we need a decorator for
Then,
I believe this formulation ensures the following:
(Note that I considered achieving this behavior with a Let me know what you think. I know this is getting a bit unwieldy, but the ability to perform cleanup on preemption seems pretty important, and it is currently only supported if the cleanup behavior can execute in a single tick. |
For what it's worth, I went ahead and implemented the above changes in my local repo. If you want to see the changes I made to your files, they are in this commit, and I'm happy to PR them into this branch if you're interested. If you want to see the The only downside of the above formulation is on preemption, |
Alright, I addressed the above issue in the PR on my repo. I also wrote comprehensive test cases, that should guarantee the following:
Achieving the above functionality required generalizing The idiom
And as I said before, I'm happy to PR the changes into this branch and/or repo -- let me know if you're interested in that! You're also welcome to re-use my test cases -- I believe the only area where my test cases may have overfit to my implementation is when it comes to the |
@stonier, any updates on this? I have multiple changes that improve upon this PR (e.g., a multi-tick generalization of OnTerminate, a re-factoring of eventually_swiss that handles preemptions). We've been using it for a year in production code, and I'd love to PR the changes into this repo. But it would be easiest/cleanest if this PR gets merged in, so my PR can directly build upon it. |
Inspired from discussion in #425 (comment).