-
Notifications
You must be signed in to change notification settings - Fork 5
Unified cleanup using scoped behaviors #103
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
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
198dbc0
Moved in decorator and idiom from py_trees#427
amalnanavati 353479e
Make OnPreempt decorator multi-tick, add on_preempt function to event…
amalnanavati 222222d
Integrate the code into this library
amalnanavati 4ac47f4
Implemented scoped behavior idiom
amalnanavati aff6e6b
Added scoped behavior to tree for concise cleanup
amalnanavati 5b40318
Added unit tests for `eventually_swiss`
amalnanavati db5019d
Updated eventually_swiss and verified that it passes tests
amalnanavati ecfa461
MoveTo bug fix
amalnanavati 73a320d
Updated test because we don't care about relative termination order b…
amalnanavati 2b23353
Update tests so that after worker/on_success/on_failure terminate, on…
amalnanavati 10233a6
Simplified eventually_swiss implementation
amalnanavati 8dbdeb4
Generalized `eventually_swiss` return status, started unit tests for …
amalnanavati fd553df
Completed scoped_behavior tests, simplified unit test generation
amalnanavati 74ef8da
Removed option to return on_failure status from eventually_swiss
amalnanavati 2e8abe5
Updated scoped_behavior to only use one post behavior
amalnanavati 9c4d229
Updated tests to preempt before the tree has started
amalnanavati 6fe55e2
Added nested test cases to scoped_behavior
amalnanavati File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
""" | ||
NOTE: This is a multi-tick version of the decorator discussed in | ||
https://github.com/splintered-reality/py_trees/pull/427 . Once a | ||
multi-tick version of that decorator is merged into py_trees, this | ||
decorator should be removed in favor of the main py_trees one. | ||
""" | ||
|
||
import time | ||
import typing | ||
|
||
from py_trees import behaviour, common | ||
from py_trees.decorators import Decorator | ||
|
||
|
||
class OnPreempt(Decorator): | ||
amalnanavati marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Behaves identically to :class:`~py_trees.decorators.PassThrough` except | ||
that if it gets preempted (i.e., `terminate(INVALID)` is called on it) | ||
while its status is :data:`~py_trees.common.Status.RUNNING`, it will | ||
tick `on_preempt` either: (a) for a single tick; or (b) until `on_preempt` | ||
reaches a status other than :data:`~py_trees.common.Status.RUNNING` or | ||
times out. Note that `on_preempt` may be a behavior that exists elsewhere | ||
in the tree, or it may be a separate behavior. | ||
|
||
This is useful to cleanup, restore a context switch or to | ||
implement a finally-like behaviour. | ||
|
||
.. seealso:: :meth:`py_trees.idioms.eventually`, :meth:`py_trees.idioms.eventually_swiss` | ||
""" | ||
|
||
# pylint: disable=too-many-arguments | ||
# This is acceptable, to give users maximum control over how this decorator | ||
# behaves. | ||
def __init__( | ||
self, | ||
name: str, | ||
child: behaviour.Behaviour, | ||
on_preempt: behaviour.Behaviour, | ||
single_tick: bool = True, | ||
period_ms: int = 0, | ||
timeout: typing.Optional[float] = None, | ||
): | ||
""" | ||
Initialise with the standard decorator arguments. | ||
|
||
Args: | ||
name: the decorator name | ||
child: the child to be decorated | ||
on_preempt: the behaviour or subtree to tick on preemption | ||
single_tick: if True, tick the child once on preemption. Else, | ||
tick the child until it reaches a status other than | ||
:data:`~py_trees.common.Status.RUNNING`. | ||
period_ms: how long to sleep between ticks (in milliseconds) | ||
if `single_tick` is False. If 0, then do not sleep. | ||
timeout: how long (sec) to wait for the child to reach a status | ||
other than :data:`~py_trees.common.Status.RUNNING` if | ||
`single_tick` is False. If None, then do not timeout. | ||
""" | ||
super().__init__(name=name, child=child) | ||
self.on_preempt = on_preempt | ||
self.single_tick = single_tick | ||
self.period_ms = period_ms | ||
self.timeout = timeout | ||
|
||
def update(self) -> common.Status: | ||
""" | ||
Just reflect the child status. | ||
|
||
Returns: | ||
the behaviour's new status :class:`~py_trees.common.Status` | ||
""" | ||
return self.decorated.status | ||
|
||
def stop(self, new_status: common.Status) -> None: | ||
""" | ||
Check if the child is running (dangling) and stop it if that is the case. | ||
|
||
This function departs from the standard :meth:`~py_trees.decorators.Decorator.stop` | ||
in that it *first* stops the child, and *then* stops the decorator. | ||
|
||
Args: | ||
new_status (:class:`~py_trees.common.Status`): the behaviour is transitioning | ||
to this new status | ||
""" | ||
self.logger.debug(f"{self.__class__.__name__}.stop({new_status})") | ||
# priority interrupt handling | ||
if new_status == common.Status.INVALID: | ||
self.decorated.stop(new_status) | ||
# if the decorator returns SUCCESS/FAILURE and should stop the child | ||
if self.decorated.status == common.Status.RUNNING: | ||
self.decorated.stop(common.Status.INVALID) | ||
self.terminate(new_status) | ||
self.status = new_status | ||
|
||
def terminate(self, new_status: common.Status) -> None: | ||
"""Tick the child behaviour once.""" | ||
self.logger.debug( | ||
f"{self.__class__.__name__}.terminate({self.status}->{new_status})" | ||
) | ||
if new_status == common.Status.INVALID and self.status == common.Status.RUNNING: | ||
terminate_start_s = time.monotonic() | ||
# Tick the child once | ||
self.on_preempt.tick_once() | ||
# If specified, tick until the child reaches a non-RUNNING status | ||
if not self.single_tick: | ||
while self.on_preempt.status == common.Status.RUNNING and ( | ||
self.timeout is None | ||
or time.monotonic() - terminate_start_s < self.timeout | ||
): | ||
if self.period_ms > 0: | ||
time.sleep(self.period_ms / 1000.0) | ||
self.on_preempt.tick_once() | ||
# Do not need to stop the child here - this method | ||
# is only called by Decorator.stop() which will handle | ||
# that responsibility immediately after this method returns. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
""" | ||
NOTE: This is a preempt-handling version of the idiom discussed in | ||
https://github.com/splintered-reality/py_trees/pull/427 . Once a | ||
preempt-handling version of that idiom is merged into py_trees, this | ||
idiom should be removed in favor of the main py_trees one. | ||
""" | ||
|
||
import typing | ||
|
||
from py_trees import behaviour, behaviours, composites | ||
|
||
from ada_feeding.decorators import OnPreempt | ||
|
||
|
||
def eventually_swiss( | ||
name: str, | ||
workers: typing.List[behaviour.Behaviour], | ||
on_failure: behaviour.Behaviour, | ||
on_success: behaviour.Behaviour, | ||
on_preempt: behaviour.Behaviour, | ||
on_preempt_single_tick: bool = True, | ||
on_preempt_period_ms: int = 0, | ||
on_preempt_timeout: typing.Optional[float] = None, | ||
return_on_success_status: bool = True, | ||
) -> behaviour.Behaviour: | ||
""" | ||
Implement a multi-tick, general purpose 'try-except-else'-like pattern. | ||
|
||
This is a swiss knife version of the eventually idiom | ||
that facilitates a multi-tick response for specialised | ||
handling work sequence's completion status. Specifically, this idiom | ||
guarentees the following: | ||
1. The on_success behaviour is ticked only if the workers all return SUCCESS. | ||
2. The on_failure behaviour is ticked only if at least one worker returns FAILURE. | ||
3. The on_preempt behaviour is ticked only if `stop(INVALID)` is called on the | ||
root behaviour returned from this idiom while the root behaviour's status is | ||
:data:`~py_trees.common.Status.RUNNING`. | ||
|
||
The return status of this idiom in non-preemption cases is: | ||
- If the workers all return SUCCESS: | ||
- If `return_on_success_status` is True, then the status of the root behaviour | ||
returned from this idiom is status of `on_success`. | ||
- If `return_on_success_status` is False, then the status of the root behaviour | ||
returned from this idiom is :data:`~py_trees.common.Status.SUCCESS`. | ||
- If at least one worker returns FAILURE, return :data:`~py_trees.common.Status.FAILURE`. | ||
|
||
.. graphviz:: dot/eventually-swiss.dot | ||
|
||
Args: | ||
name: the name to use for the idiom root | ||
workers: the worker behaviours or subtrees | ||
on_success: the behaviour or subtree to tick on work success | ||
on_failure: the behaviour or subtree to tick on work failure | ||
on_preempt: the behaviour or subtree to tick on work preemption | ||
on_preempt_single_tick: if True, tick the on_preempt behaviour once | ||
on preemption. Else, tick the on_preempt behaviour until it | ||
reaches a status other than :data:`~py_trees.common.Status.RUNNING`. | ||
on_preempt_period_ms: how long to sleep between ticks (in milliseconds) | ||
if `on_preempt_single_tick` is False. If 0, then do not sleep. | ||
on_preempt_timeout: how long (sec) to wait for the on_preempt behaviour | ||
to reach a status other than :data:`~py_trees.common.Status.RUNNING` | ||
if `on_preempt_single_tick` is False. If None, then do not timeout. | ||
return_on_success_status: if True, pass the `on_success` status to the | ||
root, else return :data:`~py_trees.common.Status.SUCCESS`. | ||
|
||
Returns: | ||
:class:`~py_trees.behaviour.Behaviour`: the root of the eventually_swiss subtree | ||
|
||
.. seealso:: :meth:`py_trees.idioms.eventually`, :ref:`py-trees-demo-eventually-swiss-program` | ||
""" | ||
# pylint: disable=too-many-arguments, too-many-locals | ||
# This is acceptable, to give users maximum control over how this swiss-knife | ||
# idiom behaves. | ||
# pylint: disable=abstract-class-instantiated | ||
# behaviours.Failure and behaviours.Success are valid instantiations | ||
|
||
workers_sequence = composites.Sequence( | ||
name="Workers", | ||
memory=True, | ||
children=workers, | ||
) | ||
on_failure_return_status = composites.Sequence( | ||
name="On Failure Return Failure", | ||
memory=True, | ||
children=[on_failure, behaviours.Failure(name="Failure")], | ||
) | ||
on_failure_subtree = composites.Selector( | ||
name="On Failure", | ||
memory=True, | ||
children=[workers_sequence, on_failure_return_status], | ||
) | ||
if return_on_success_status: | ||
on_success_return_status = on_success | ||
else: | ||
on_success_return_status = composites.Selector( | ||
name="On Success Return Success", | ||
memory=True, | ||
children=[on_success, behaviours.Success(name="Success")], | ||
) | ||
on_success_subtree = composites.Sequence( | ||
name="On Success", | ||
memory=True, | ||
children=[on_failure_subtree, on_success_return_status], | ||
) | ||
root = OnPreempt( | ||
name=name, | ||
child=on_success_subtree, | ||
on_preempt=on_preempt, | ||
single_tick=on_preempt_single_tick, | ||
period_ms=on_preempt_period_ms, | ||
timeout=on_preempt_timeout, | ||
) | ||
|
||
return root |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.