py_trees.composites module
Composites (multi-child) types for behaviour trees.
Composites are responsible for directing the path traced through the tree on a given tick (execution). They are the factories (Sequences and Parallels) and decision makers (Selectors) of a behaviour tree.
Composite behaviours typically manage children and apply some logic to the way they execute and return a result, but generally don’t do anything themselves. Perform the checks or actions you need to do in the non-composite behaviours.
Most any desired functionality can be authored with a combination of these three composites. In fact, it is precisely this feature that makes behaviour trees attractive - it breaks down complex decision making logic to just three primitive elements. It is possible and often desirable to extend this set with custom composites of your own, but think carefully before you do - in almost every case, a combination of the existing composites will serve and as a result, you will merely compound the complexity inherent in your tree logic. This this makes it confoundingly difficult to design, introspect and debug. As an example, design sessions often revolve around a sketched graph on a whiteboard. When these graphs are composed of just five elements (Selectors, Sequences, Parallels, Decorators and Behaviours), it is very easy to understand the logic at a glance. Double the number of fundamental elements and you may as well be back at the terminal parsing code.
Tip
You should never need to subclass or create new composites.
The basic operational modes of the three composites in this library are as follows:
Selector
: execute a child based on cascading prioritiesSequence
: execute children sequentiallyParallel
: execute children concurrently
This library does provide some flexibility in how each composite is implemented without breaking the fundamental nature of each (as described above). Selectors and Sequences can be configured with or without memory (resumes or resets if children are RUNNING) and the results of a parallel can be configured to wait upon all children completing, succeed on one, all or a subset thereof.
Tip
Follow the links in each composite’s documentation to the relevant demo programs.
- class py_trees.composites.Composite(name: str, children: Sequence[Behaviour] | None = None)
Bases:
Behaviour
,ABC
The parent class to all composite behaviours.
- Args:
name (
str
): the composite behaviour name children ([Behaviour
]): list of children to add
- add_child(child: Behaviour) UUID
Add a child.
- Args:
child: child to add
- Raises:
TypeError: if the child is not an instance of
Behaviour
RuntimeError: if the child already has a parent- Returns:
unique id of the child
- add_children(children: List[Behaviour]) Behaviour
Append a list of children to the current list.
- Args:
children ([
Behaviour
]): list of children to add
- insert_child(child: Behaviour, index: int) UUID
Insert child at the specified index.
This simply directly calls the python list’s
insert
method using the child and index arguments.- Args:
child (
Behaviour
): child to insert index (int
): index to insert it at- Returns:
uuid.UUID: unique id of the child
- prepend_child(child: Behaviour) UUID
Prepend the child before all other children.
- Args:
child: child to insert
- Returns:
uuid.UUID: unique id of the child
- remove_all_children() None
Remove all children. Makes sure to stop each child if necessary.
- remove_child(child: Behaviour) int
Remove the child behaviour from this composite.
- Args:
child: child to delete
- Returns:
index of the child that was removed
- remove_child_by_id(child_id: UUID) None
Remove the child with the specified id.
- Args:
child_id: unique id of the child
- Raises:
IndexError: if the child was not found
- replace_child(child: Behaviour, replacement: Behaviour) None
Replace the child behaviour with another.
- Args:
child: child to delete replacement: child to insert
- stop(new_status: Status = Status.INVALID) None
Provide common stop-level functionality for all composites.
The latter situation can arise for some composites, but more importantly, will always occur when high higher priority behaviour interrupts this one.
- Args:
new_status: behaviour will transition to this new status
- abstract tick() Iterator[Behaviour]
Tick the composite.
All composite subclasses require a re-implementation of the tick method to provide the logic for managing multiple children (
tick()
merely provides default logic for when there are no children).
- tip() Behaviour | None
Recursive function to extract the last running node of the tree.
- Returns:
the tip function of the current child of this composite or None
- update() Status
Unused update method.
Composites should direct the flow, whilst behaviours do the real work.
Such flows are a consequence of how the composite interacts with it’s children. The success of behaviour trees depends on this logic being simple, well defined and limited to a few well established patterns - this is what ensures that visualising a tree enables a user to quickly grasp the decision making captured therein.
For the standard patterns, this logic is limited to the ordering of execution and logical inferences on the resulting status of the composite’s children.
This is a good guideline to adhere to (i.e. don’t reach inside children to inference on custom variables, nor reach out to the system your tree is attached to).
Implementation wise, this renders the
update()
method redundant as all customisation to create a simple, well defined composite happens in thetick()
method.Bottom line, composites do not make use of this method. Implementing it for subclasses of the core composites will not do anything.
- class py_trees.composites.Parallel(name: str, policy: Base, children: Sequence[Behaviour] | None = None)
Bases:
Composite
Parallels enable a kind of spooky at-a-distance concurrency.
A parallel ticks every child every time the parallel is itself ticked. The parallelism however, is merely conceptual. The children have actually been sequentially ticked, but from both the tree and the parallel’s purview, all children have been ticked at once.
The parallelism too, is not true in the sense that it kicks off multiple threads or processes to do work. Some behaviours may kick off threads or processes in the background, or connect to existing threads/processes. The behaviour itself however, merely monitors these and is itself encosced in a py_tree which only ever ticks in a single-threaded operation.
Parallels with policy
SuccessOnAll
only returnsSUCCESS
if all children returnSUCCESS
Parallels with policy
SuccessOnOne
returnSUCCESS
if at least one child returnsSUCCESS
and others areRUNNING
Parallels with policy
SuccessOnSelected
only returnsSUCCESS
if a specified subset of children returnSUCCESS
Policies
SuccessOnAll
andSuccessOnSelected
may be configured to be synchronised in which case children that tick withSUCCESS
will be skipped on subsequent ticks until the policy criteria is met, or one of the children returns statusFAILURE
.Parallels with policy
SuccessOnSelected
will check in both thesetup()
andtick()
methods to to verify the selected set of children is actually a subset of the children of this parallel.See also
Context Switching Demo
- setup(**kwargs: int) None
Detect before ticking whether the policy configuration is invalid.
- Args:
- **kwargs (
dict
): distribute arguments to this behaviour and in turn, all of it’s children
- **kwargs (
- Raises:
RuntimeError: if the parallel’s policy configuration is invalid Exception: be ready to catch if any of the children raise an exception
- stop(new_status: Status = Status.INVALID) None
Ensure that any running children are stopped.
- Args:
new_status : the composite is transitioning to this new status
- tick() Iterator[Behaviour]
Tick over the children.
- Yields:
Behaviour
: a reference to itself or one of its children- Raises:
RuntimeError: if the policy configuration was invalid
- validate_policy_configuration() None
Validate the currently stored policy.
Policy configuration can be invalid if: * Policy is SuccessOnSelected and no behaviours have been specified * Policy is SuccessOnSelected and behaviours that are not children exist
- Raises:
RuntimeError: if policy configuration was invalid
- class py_trees.composites.Selector(name: str, memory: bool, children: Sequence[Behaviour] | None = None)
Bases:
Composite
Selectors are the decision makers.
A selector executes each of its child behaviours in turn until one of them succeeds (at which point it itself returns
RUNNING
orSUCCESS
, or it runs out of children at which point it itself returnsFAILURE
. We usually refer to selecting children as a means of choosing between priorities. Each child and its subtree represent a decreasingly lower priority path.Note
Switching from a low -> high priority branch causes a stop(INVALID) signal to be sent to the previously executing low priority branch. This signal will percolate down that child’s own subtree. Behaviours should make sure that they catch this and destruct appropriately.
Note
If configured with memory, higher priority checks will be skipped when a child returned with running on the previous tick. i.e. once a priority is locked in, it will run to completion and can only be interrupted if the selector is interrupted by higher priorities elsewhere in the tree.
See also
The py-trees-demo-selector-program program demos higher priority switching under a selector.
- Args:
-
name (
str
): the composite behaviour name children ([Behaviour
]): list of children to add
- class py_trees.composites.Sequence(name: str, memory: bool, children: Sequence[Behaviour] | None = None)
Bases:
Composite
Sequences are the factory lines of behaviour trees.
A sequence will progressively tick over each of its children so long as each child returns
SUCCESS
. If any child returnsFAILURE
orRUNNING
the sequence will halt and the parent will adopt the result of this child. If it reaches the last child, it returns with that result regardless.Note
The sequence halts once it engages with a child is RUNNING, remaining behaviours are not ticked.
Note
If configured with memory and a child returned with running on the previous tick, it will proceed directly to the running behaviour, skipping any and all preceding behaviours. With memory is useful for moving through a long running series of tasks. Without memory is useful if you want conditional guards in place preceding the work that you always want checked off.
See also
The py-trees-demo-sequence-program program demos a simple sequence in action.
- Args:
name: the composite behaviour name memory: if
RUNNING
on the previous tick,resume with the
RUNNING
childchildren: list of children to add