Blackboards¶
Blackboards are not a necessary component of behaviour tree implementations, but are nonetheless, a fairly common mechanism for for sharing data between behaviours in the tree. See, for example, the design notes for blackboards in Unreal Engine.

Implementations vary widely depending on the needs of the framework using them. The simplest implementations take the form of a key-value store with global access, while more rigorous implementations scope access and form a secondary graph overlaying the tree graph connecting data ports between behaviours.
The implementation here strives to remain simple to use (so ‘rapid development’ does not become just ‘development’), yet sufficiently featured so that the magic behind the scenes (i.e. the data sharing on the blackboard) is exposed and helpful in debugging tree applications.
To be more concrete, the following is a list of features that this implementation either embraces or does not.
- [+] Centralised key-value store
- [+] Client based usage with registration of read/write intentions at construction
- [+] Activity stream that tracks read/write operations by behaviours
- [-] Sharing between tree instances
- [-] Exclusive locks for reading/writing
- [-] Priority policies for variable instantiations
The primary user-facing interface with the blackboard is via the BlackboardClient.
-
class
py_trees.blackboard.
BlackboardClient
(*, name=None, unique_identifier=None, read=None, write=None)[source] Client to the key-value store for sharing data between behaviours.
Examples
Blackboard clients will accept a user-friendly name / unique identifier for registration on the centralised store or create them for you if none is provided.
provided = py_trees.blackboard.BlackboardClient( name="Provided", unique_identifier=uuid.uuid4() ) print(provided) generated = py_trees.blackboard.BlackboardClient() print(generated)
Client Instantiation
Register read/write access for keys on the blackboard. Note, registration is not initialisation.
blackboard = py_trees.blackboard.BlackboardClient( name="Client", read={"foo"}, write={"bar"} ) blackboard.register_key(key="foo", write=True) blackboard.foo = "foo" print(blackboard)
Variable Read/Write Registration
Disconnected instances will discover the centralised key-value store.
def check_foo(): blackboard = py_trees.blackboard.BlackboardClient(name="Reader", read={"foo"}) print("Foo: {}".format(blackboard.foo)) blackboard = py_trees.blackboard.BlackboardClient(name="Writer", write={"foo"}) blackboard.foo = "bar" check_foo()
To respect an already initialised key on the blackboard:
blackboard = BlackboardClient(name="Writer", read={"foo")) result = blackboard.set("foo", "bar", overwrite=False)
Store complex objects on the blackboard:
class Nested(object): def __init__(self): self.foo = None self.bar = None def __str__(self): return str(self.__dict__) writer = py_trees.blackboard.BlackboardClient( name="Writer", write={"nested"} ) reader = py_trees.blackboard.BlackboardClient( name="Reader", read={"nested"} ) writer.nested = Nested() writer.nested.foo = "foo" writer.nested.bar = "bar" foo = reader.nested.foo print(writer) print(reader)
Log and display the activity stream:
py_trees.blackboard.Blackboard.enable_activity_stream(maximum_size=100) blackboard_reader = py_trees.blackboard.BlackboardClient(name="Reader", read={"foo"}) blackboard_writer = py_trees.blackboard.BlackboardClient(name="Writer", write={"foo"}) blackboard_writer.foo = "bar" blackboard_writer.foo = "foobar" unused_result = blackboard_reader.foo print(py_trees.display.unicode_blackboard_activity_stream()) py_trees.blackboard.Blackboard.activity_stream.clear()
Display the blackboard on the console, or part thereof:
writer = py_trees.blackboard.BlackboardClient( name="Writer", write={"foo", "bar", "dude", "dudette"} ) reader = py_trees.blackboard.BlackboardClient( name="Reader", read={"foo", "bBlackboardClient( ) writer.foo = "foo" writer.bar = "bar" writer.dude = "bob" # all key-value pairs print(py_trees.display.unicode_blackboard()) # various filtered views print(py_trees.display.unicode_blackboard(key_filter={"foo"})) print(py_trees.display.unicode_blackboard(regex_filter="dud*")) print(py_trees.display.unicode_blackboard(client_filter={reader.unique_identifier})) # list the clients associated with each key print(py_trees.display.unicode_blackboard(display_only_key_metadata=True))
Behaviours register their own blackboard clients with the same name/id as the behaviour itself. This helps associate blackboard variables with behaviours, enabling various introspection and debugging capabilities on the behaviour trees.
Creating a custom behaviour with blackboard variables:
class Foo(py_trees.behaviours.Behaviour): def __init__(self, name): super().__init__(name=name) self.blackboard.register_key("foo", read=True) def update(self): self.feedback_message = self.blackboard.foo return py_trees.common.Status.Success
Rendering a dot graph for a behaviour tree, complete with blackboard variables:
# in code py_trees.display.render_dot_tree(py_trees.demos.blackboard.create_root()) # command line tools py-trees-render --with-blackboard-variables py_trees.demos.blackboard.create_root
And to demonstrate that it doesn’t become a tangled nightmare at scale, an example of a more complex tree:
With judicious use of the display methods / activity stream around the ticks of a tree (refer to
py_trees.visitors.DisplaySnapshotVisitor
for examplar code):See also
- py-trees-demo-blackboard
py_trees.visitors.DisplaySnapshotVisitor
py_trees.behaviours.SetBlackboardVariable
py_trees.behaviours.UnsetBlackboardVariable
py_trees.behaviours.CheckBlackboardVariableExists
py_trees.behaviours.WaitForBlackboardVariable
py_trees.behaviours.CheckBlackboardVariableValue
py_trees.behaviours.WaitForBlackboardVariableValue
Variables: - name (str) – client’s convenient, but not necessarily unique identifier
- unique_identifier (uuid.UUID) – client’s unique identifier
- read (typing.List[str]) – keys this client has permission to read
- write (typing.List[str]) – keys this client has permission to write