193 lines
7.5 KiB
Python
193 lines
7.5 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# License: BSD
|
||
|
# https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
|
||
|
#
|
||
|
##############################################################################
|
||
|
# Documentation
|
||
|
##############################################################################
|
||
|
|
||
|
"""
|
||
|
Visitors are entities that can be passed to a tree implementation
|
||
|
(e.g. :class:`~py_trees.trees.BehaviourTree`) and used to either visit
|
||
|
each and every behaviour in the tree, or visit behaviours as the tree is
|
||
|
traversed in an executing tick. At each behaviour, the visitor
|
||
|
runs its own method on the behaviour to do as it wishes - logging, introspecting, etc.
|
||
|
|
||
|
.. warning:: Visitors should not modify the behaviours they visit.
|
||
|
"""
|
||
|
|
||
|
##############################################################################
|
||
|
# Imports
|
||
|
##############################################################################
|
||
|
|
||
|
from . import blackboard
|
||
|
from . import display
|
||
|
|
||
|
##############################################################################
|
||
|
# Visitors
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
class VisitorBase(object):
|
||
|
"""
|
||
|
Parent template for visitor types.
|
||
|
|
||
|
Visitors are primarily designed to work with :class:`~py_trees.trees.BehaviourTree`
|
||
|
but they can be used in the same way for other tree custodian implementations.
|
||
|
|
||
|
Args:
|
||
|
full (:obj:`bool`): flag to indicate whether it should be used to visit only traversed nodes or the entire tree
|
||
|
|
||
|
Attributes:
|
||
|
full (:obj:`bool`): flag to indicate whether it should be used to visit only traversed nodes or the entire tree
|
||
|
"""
|
||
|
def __init__(self, full=False):
|
||
|
self.full = full
|
||
|
|
||
|
def initialise(self):
|
||
|
"""
|
||
|
Override this method if any resetting of variables needs to be
|
||
|
performed between ticks (i.e. visitations).
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
def finalise(self):
|
||
|
"""
|
||
|
Override this method if any work needs to be
|
||
|
performed after ticks (i.e. showing data).
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
def run(self, behaviour):
|
||
|
"""
|
||
|
This method gets run as each behaviour is ticked. Override it to
|
||
|
perform some activity - e.g. introspect the behaviour
|
||
|
to store/process logging data for visualisations.
|
||
|
|
||
|
Args:
|
||
|
behaviour (:class:`~py_trees.behaviour.Behaviour`): behaviour that is ticking
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class DebugVisitor(VisitorBase):
|
||
|
"""
|
||
|
Picks up and logs feedback messages and the behaviour's status. Logging is done with
|
||
|
the behaviour's logger.
|
||
|
"""
|
||
|
def __init__(self):
|
||
|
super(DebugVisitor, self).__init__(full=False)
|
||
|
|
||
|
def run(self, behaviour):
|
||
|
if behaviour.feedback_message:
|
||
|
behaviour.logger.debug("%s.run() [%s][%s]" % (self.__class__.__name__, behaviour.feedback_message, behaviour.status))
|
||
|
else:
|
||
|
behaviour.logger.debug("%s.run() [%s]" % (self.__class__.__name__, behaviour.status))
|
||
|
|
||
|
|
||
|
class SnapshotVisitor(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
|
||
|
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
|
||
|
visited_blackboard_ids(typing.Set[uuid.UUID]): blackboard client id's on the visited path
|
||
|
visited_blackboard_keys(typing.Set[str]): blackboard variable keys on the visited path
|
||
|
|
||
|
.. seealso:: The :ref:`py-trees-demo-logging-program` program demonstrates use of this visitor to trigger logging of a tree serialisation.
|
||
|
"""
|
||
|
def __init__(self):
|
||
|
super().__init__(full=False)
|
||
|
self.changed = False
|
||
|
self.visited = {}
|
||
|
self.previously_visited = {}
|
||
|
self.visited_blackboard_keys = set()
|
||
|
self.visited_blackboard_client_ids = set()
|
||
|
|
||
|
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_visited = self.visited
|
||
|
self.visited = {}
|
||
|
self.visited_blackboard_keys = set()
|
||
|
self.visited_blackboard_client_ids = set()
|
||
|
|
||
|
def run(self, behaviour):
|
||
|
"""
|
||
|
This method gets run as each behaviour is ticked. Catch the id and status and store it.
|
||
|
Additionally add it to the running list if it is :data:`~py_trees.common.Status.RUNNING`.
|
||
|
|
||
|
Args:
|
||
|
behaviour (:class:`~py_trees.behaviour.Behaviour`): behaviour that is ticking
|
||
|
"""
|
||
|
# behaviour status
|
||
|
self.visited[behaviour.id] = behaviour.status
|
||
|
try:
|
||
|
if self.visited[behaviour.id] != self.previously_visited[behaviour.id]:
|
||
|
self.changed = True
|
||
|
except KeyError:
|
||
|
self.changed = True
|
||
|
# blackboards
|
||
|
for blackboard in behaviour.blackboards:
|
||
|
self.visited_blackboard_client_ids.add(blackboard.id())
|
||
|
self.visited_blackboard_keys = self.visited_blackboard_keys | blackboard.read | blackboard.write | blackboard.exclusive
|
||
|
|
||
|
|
||
|
class DisplaySnapshotVisitor(SnapshotVisitor):
|
||
|
"""
|
||
|
Visit the tree, capturing the visited path, it's changes since the last
|
||
|
tick and additionally print the snapshot to console.
|
||
|
|
||
|
Args:
|
||
|
display_blackboard: print to the console the relevant part of the blackboard associated with behaviours on the visited path
|
||
|
display_activity_stream: print to the console a log of the activity on the blackboard over the last tick
|
||
|
"""
|
||
|
def __init__(
|
||
|
self,
|
||
|
display_only_visited_behaviours=False,
|
||
|
display_blackboard: bool=False,
|
||
|
display_activity_stream: bool=False
|
||
|
):
|
||
|
super().__init__()
|
||
|
self.display_only_visited_behaviours = display_only_visited_behaviours
|
||
|
self.display_blackboard = display_blackboard
|
||
|
self.display_activity_stream = display_activity_stream
|
||
|
if self.display_activity_stream:
|
||
|
blackboard.Blackboard.enable_activity_stream()
|
||
|
|
||
|
def initialise(self):
|
||
|
self.root = None
|
||
|
super().initialise()
|
||
|
if self.display_activity_stream:
|
||
|
blackboard.Blackboard.activity_stream.clear()
|
||
|
|
||
|
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_only_visited=self.display_only_visited_behaviours,
|
||
|
show_status=False,
|
||
|
visited=self.visited,
|
||
|
previously_visited=self.previously_visited
|
||
|
)
|
||
|
)
|
||
|
if self.display_blackboard:
|
||
|
print(display.unicode_blackboard(key_filter=self.visited_blackboard_keys))
|
||
|
if self.display_activity_stream:
|
||
|
print(display.unicode_blackboard_activity_stream())
|