Skip to content
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

[visitors] snapshot/winds of change functionality collapsed #228

Merged
merged 2 commits into from
Sep 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ Release Notes
Forthcoming
-----------

1.3.x (2019-??-??)
------------------

**Breaking API**

* [visitors] collapsed ``SnapshotVisitor`` and ``WindsOfChangeVisitor`` functionality, `#228 <https://github.com/splintered-reality/py_trees/pull/228>`_

**New Features**

* [visitors] new ``DisplaySnapshotVisitor`` to simplify collection/printing the tree to console, `#228 <https://github.com/splintered-reality/py_trees/pull/228>`_

1.2.2 (2019-08-06)
------------------
* [trees] standalone ``setup()`` method with timer for use on unmanaged trees, `#198 <https://github.com/splintered-reality/py_trees/pull/198>`_
Expand Down
26 changes: 6 additions & 20 deletions py_trees/demos/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

def description(root):
content = "A demonstration of logging with trees.\n\n"
content += "This demo utilises a WindsOfChange visitor to trigger\n"
content += "This demo utilises a SnapshotVisitor to trigger\n"
content += "a post-tick handler to dump a serialisation of the\n"
content += "tree to a json log file.\n"
content += "\n"
Expand Down Expand Up @@ -81,11 +81,11 @@ def command_line_argument_parser():
return parser


def logger(winds_of_change_visitor, behaviour_tree):
def logger(snapshot_visitor, behaviour_tree):
"""
A post-tick handler that logs the tree (relevant parts thereof) to a yaml file.
"""
if winds_of_change_visitor.changed:
if snapshot_visitor.changed:
print(console.cyan + "Logging.......................yes\n" + console.reset)
tree_serialisation = {
'tick': behaviour_tree.count,
Expand All @@ -109,7 +109,7 @@ def logger(winds_of_change_visitor, behaviour_tree):
'type': node_type_str,
'status': node.status.value,
'message': node.feedback_message,
'is_active': True if node.id in winds_of_change_visitor.ticked_nodes else False
'is_active': True if node.id in snapshot_visitor.visited else False
}
tree_serialisation['nodes'].append(node_snapshot)
if behaviour_tree.count == 0:
Expand All @@ -122,17 +122,6 @@ def logger(winds_of_change_visitor, behaviour_tree):
print(console.yellow + "Logging.......................no\n" + console.reset)


def display_unicode_tree(snapshot_visitor, behaviour_tree):
"""
Prints an ascii tree with the current snapshot status.
"""
print("\n" + py_trees.display.unicode_tree(
behaviour_tree.root,
visited=snapshot_visitor.visited,
previously_visited=snapshot_visitor.previously_visited)
)


def create_tree():
every_n_success = py_trees.behaviours.SuccessEveryN("EveryN", 5)
sequence = py_trees.composites.Sequence(name="Sequence")
Expand Down Expand Up @@ -177,15 +166,12 @@ def main():
behaviour_tree = py_trees.trees.BehaviourTree(tree)

debug_visitor = py_trees.visitors.DebugVisitor()
snapshot_visitor = py_trees.visitors.SnapshotVisitor()
winds_of_change_visitor = py_trees.visitors.WindsOfChangeVisitor()
snapshot_visitor = py_trees.visitors.DisplaySnapshotVisitor()

behaviour_tree.visitors.append(debug_visitor)
behaviour_tree.visitors.append(snapshot_visitor)
behaviour_tree.visitors.append(winds_of_change_visitor)

behaviour_tree.add_post_tick_handler(functools.partial(display_unicode_tree, snapshot_visitor))
behaviour_tree.add_post_tick_handler(functools.partial(logger, winds_of_change_visitor))
behaviour_tree.add_post_tick_handler(functools.partial(logger, snapshot_visitor))

behaviour_tree.setup(timeout=15)

Expand Down
15 changes: 1 addition & 14 deletions py_trees/demos/stewardship.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,6 @@ def pre_tick_handler(behaviour_tree):
print("\n--------- Run %s ---------\n" % behaviour_tree.count)


def post_tick_handler(snapshot_visitor, behaviour_tree):
"""
Prints an ascii tree with the current snapshot status.
"""
print("\n" + py_trees.display.unicode_tree(
root=behaviour_tree.root,
visited=snapshot_visitor.visited,
previously_visited=snapshot_visitor.visited)
)


def create_tree():
every_n_success = py_trees.behaviours.SuccessEveryN("EveryN", 5)
sequence = py_trees.composites.Sequence(name="Sequence")
Expand Down Expand Up @@ -147,9 +136,7 @@ def main():
behaviour_tree = py_trees.trees.BehaviourTree(tree)
behaviour_tree.add_pre_tick_handler(pre_tick_handler)
behaviour_tree.visitors.append(py_trees.visitors.DebugVisitor())
snapshot_visitor = py_trees.visitors.SnapshotVisitor()
behaviour_tree.add_post_tick_handler(functools.partial(post_tick_handler, snapshot_visitor))
behaviour_tree.visitors.append(snapshot_visitor)
behaviour_tree.visitors.append(py_trees.visitors.DisplaySnapshotVisitor())
behaviour_tree.setup(timeout=15)

####################
Expand Down
2 changes: 1 addition & 1 deletion py_trees/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def tick_tree(root,
for visitor in visitors:
node.visit(visitor)
if print_snapshot:
print(console.green + "\nAscii Tree Snapshot" + console.reset)
print(console.green + "\nTree Snapshot" + console.reset)
print(display.unicode_tree(root=root, show_status=True))
if print_blackboard:
print(str(blackboard.Blackboard()))
Expand Down
35 changes: 1 addition & 34 deletions py_trees/trees.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def add_visitor(self, visitor):

.. seealso:: :class:`~py_trees.visitors.DebugVisitor`,
:class:`~py_trees.visitors.SnapshotVisitor`,
:class:`~py_trees.visitors.WindsOfChangeVisitor`
:class:`~py_trees.visitors.DisplaySnapshotVisitor`
"""
self.visitors.append(visitor)

Expand Down Expand Up @@ -405,36 +405,3 @@ def shutdown(self):
# timeout mechanisms as used in setup()
for node in self.root.iterate():
node.shutdown()

##############################################################################
# Post Tick Handlers
##############################################################################


def setup_tree_unicode_art_debug(tree: BehaviourTree):
"""
Convenience method for configuring a tree to paint unicode art
for your tree's snapshot on your console at the end of every tick.

Args:
tree (:class:`~py_trees.trees.BehaviourTree`): the behaviour tree that has just been ticked
"""
def unicode_tree_post_tick_handler(
snapshot_visitor: visitors.SnapshotVisitor,
tree: BehaviourTree
):
print(
display.unicode_tree(
tree.root,
visited=snapshot_visitor.visited,
previously_visited=snapshot_visitor.previously_visited
)
)
snapshot_visitor = visitors.SnapshotVisitor()
tree.add_visitor(snapshot_visitor)
tree.add_post_tick_handler(
functools.partial(
unicode_tree_post_tick_handler,
snapshot_visitor
)
)
86 changes: 38 additions & 48 deletions py_trees/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
# Imports
##############################################################################

from . import display

##############################################################################
# Visitors
##############################################################################
Expand Down Expand Up @@ -84,55 +86,15 @@ def run(self, behaviour):


class SnapshotVisitor(VisitorBase):
"""
Visits the tree in tick-tock, recording the id/status of the visited set
of nodes. Additionally caches the last tick's visited collection for
comparison.

Args:
full (:obj:`bool`): flag to indicate whether it should be used to visit only traversed nodes or the entire tree

Attributes:
visited (dict): dictionary of behaviour id (uuid.UUID) and status (:class:`~py_trees.common.Status`) pairs
previously_visited (dict): dictionary of behaviour id's saved from the previous tree tick

.. seealso::

This visitor is used with the :class:`~py_trees.trees.BehaviourTree` class to collect
information and :meth:`py_trees.display.unicode_tree` to display information.
"""
def __init__(self, full=False):
super(SnapshotVisitor, self).__init__(full=full)
self.visited = {}
self.previously_visited = {}

def initialise(self):
"""
Cache the last collection of visited nodes and reset the dictionary.
"""
self.previously_visited = self.visited
self.visited = {}

def run(self, behaviour):
"""
This method gets run as each behaviour is ticked. Catch the id and status and store it.

Args:
behaviour (:class:`~py_trees.behaviour.Behaviour`): behaviour that is ticking
"""
self.visited[behaviour.id] = behaviour.status


class WindsOfChangeVisitor(VisitorBase):
"""
Visits the ticked part of a tree, checking off the status against the set of status
results recorded in the previous tick. If there has been a change, it flags it.
This is useful for determining when to trigger, e.g. logging.

Attributes:
changed (Bool): flagged if there is a difference in the visited path or :class:`~py_trees.common.Status` of any behaviour on the path
ticked_nodes (dict): dictionary of behaviour id (uuid.UUID) and status (:class:`~py_trees.common.Status`) pairs from the current tick
previously_ticked+nodes (dict): dictionary of behaviour id (uuid.UUID) and status (:class:`~py_trees.common.Status`) pairs from the previous tick
visited (dict): dictionary of behaviour id (uuid.UUID) and status (:class:`~py_trees.common.Status`) pairs from the current tick
previously_visited (dict): dictionary of behaviour id (uuid.UUID) and status (:class:`~py_trees.common.Status`) pairs from the previous tick
running_nodes([uuid.UUID]): list of id's for behaviours which were traversed in the current tick
previously_running_nodes([uuid.UUID]): list of id's for behaviours which were traversed in the last tick

Expand All @@ -141,17 +103,17 @@ class WindsOfChangeVisitor(VisitorBase):
def __init__(self):
super().__init__(full=False)
self.changed = False
self.ticked_nodes = {}
self.previously_ticked_nodes = {}
self.visited = {}
self.previously_visited = {}

def initialise(self):
"""
Switch running to previously running and then reset all other variables. This should
get called before a tree ticks.
"""
self.changed = False
self.previously_ticked_nodes = self.ticked_nodes
self.ticked_nodes = {}
self.previously_visited = self.visited
self.visited = {}

def run(self, behaviour):
"""
Expand All @@ -161,9 +123,37 @@ def run(self, behaviour):
Args:
behaviour (:class:`~py_trees.behaviour.Behaviour`): behaviour that is ticking
"""
self.ticked_nodes[behaviour.id] = behaviour.status
self.visited[behaviour.id] = behaviour.status
try:
if self.ticked_nodes[behaviour.id] != self.previously_ticked_nodes[behaviour.id]:
if self.visited[behaviour.id] != self.previously_visited[behaviour.id]:
self.changed = True
except KeyError:
self.changed = True


class DisplaySnapshotVisitor(SnapshotVisitor):
"""
Visit the tree, capturing the visited path, it's changes since the last
tick and additionally print the snapshot to console.
"""
def __init__(self):
super().__init__()

def initialise(self):
self.root = None
super().initialise()

def run(self, behaviour):
self.root = behaviour # last behaviour visited will always be the root
super().run(behaviour)

def finalise(self):
print(
"\n" +
display.unicode_tree(
root=self.root,
show_status=False,
visited=self.visited,
previously_visited=self.previously_visited
)
)
10 changes: 6 additions & 4 deletions tests/test_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,24 +652,26 @@ def finalise(self):

def test_unicode_tree_debug():
"""
Just check the code path through to painting ascii art via
tree visitors and post-tick handlers is executable
Just check the code path through to painting unicode art via
tree visitors is executable
"""
console.banner("Tree Ascii Art")
root = py_trees.composites.Selector("Selector")
root.add_child(
py_trees.behaviours.Count(
name="High Priority",
fail_until=1,
running_until=1,
running_until=3,
success_until=10
)
)
root.add_child(py_trees.behaviours.Running(name="Low Priority"))
tree = py_trees.trees.BehaviourTree(root=root)
py_trees.trees.setup_tree_unicode_art_debug(tree)
tree.add_visitor(py_trees.visitors.DisplaySnapshotVisitor())
tree.setup()
tree.tick()
tree.tick()
tree.tick()
# If we got all the way here, that suffices. If we really wished,
# we could catch stdout and check that.
assert(True)
12 changes: 5 additions & 7 deletions tests/test_visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
##############################################################################


def test_winds_of_change():
console.banner("Winds of Change")
def test_snapshot_visitor():
console.banner("Snapshot Visitor")

root = py_trees.composites.Selector(name='Selector')
a = py_trees.behaviours.Count(name="A")
Expand All @@ -44,18 +44,16 @@ def test_winds_of_change():

debug_visitor = py_trees.visitors.DebugVisitor()
snapshot_visitor = py_trees.visitors.SnapshotVisitor()
winds_of_change_visitor = py_trees.visitors.WindsOfChangeVisitor()

for i, result in zip(range(1, 5), [True, False, False, True]):
py_trees.tests.tick_tree(
root, i, i,
visitors=[debug_visitor,
snapshot_visitor,
winds_of_change_visitor],
snapshot_visitor],
print_snapshot=True
)
print("--------- Assertions ---------\n")
print("winds_of_change_visitor.changed == {}".format(result))
assert(winds_of_change_visitor.changed is result)
print("snapshot_visitor.changed == {}".format(result))
assert(snapshot_visitor.changed is result)

print("Done")