Skip to content

Commit c703efd

Browse files
committed
Implement coroutine-based Python nodes.
1 parent 4448376 commit c703efd

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

python_examples/btpy.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,44 @@ def specify_ports(cls):
2323
return cls
2424

2525
return specify_ports
26+
27+
28+
class AsyncActionNode(StatefulActionNode):
29+
"""An abstract action node implemented via cooperative multitasking.
30+
31+
Subclasses must implement the `run()` method as a generator. Optionally,
32+
this method can return a final `NodeStatus` value to indicate its exit
33+
condition.
34+
35+
Note:
36+
It is the responsibility of the action author to not block the main
37+
behavior tree loop with long-running tasks. `yield` calls should be
38+
placed whenever a pause is appropriate.
39+
"""
40+
41+
def __init__(self, name, config):
42+
super().__init__(name, config)
43+
44+
def on_start(self):
45+
self.coroutine = self.run()
46+
return NodeStatus.RUNNING
47+
48+
def on_running(self):
49+
# The library logic should never allow this to happen, but users can
50+
# still manually call `on_running` without an associated `on_start`
51+
# call. Make sure to print a useful error when this happens.
52+
if self.coroutine is None:
53+
raise "AsyncActionNode run without starting"
54+
55+
# Resume the coroutine (generator). As long as the generator is not
56+
# exhausted, keep this action in the RUNNING state.
57+
try:
58+
next(self.coroutine)
59+
return NodeStatus.RUNNING
60+
except StopIteration as e:
61+
# If the action returns a status then propagate it upwards.
62+
if e.value is not None:
63+
return e.value
64+
# Otherwise, just assume the action finished successfully.
65+
else:
66+
return NodeStatus.SUCCESS

python_examples/ex06_async_nodes.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Demonstration of an asynchronous action node implemented conveniently as a
5+
Python coroutine. This enables simple synchronous code to be written in place of
6+
complex asynchronous state machines.
7+
"""
8+
9+
import time
10+
import numpy as np
11+
from btpy import (
12+
AsyncActionNode,
13+
BehaviorTreeFactory,
14+
SyncActionNode,
15+
NodeStatus,
16+
ports,
17+
)
18+
19+
20+
xml_text = """
21+
<root BTCPP_format="4" >
22+
23+
<BehaviorTree ID="MainTree">
24+
<ReactiveSequence name="root">
25+
<Print value="{command}" />
26+
<MyAsyncNode start="[0.1, -0.2]" goal="[-0.6, 0.3]" command="{command}" />
27+
</ReactiveSequence>
28+
</BehaviorTree>
29+
30+
</root>
31+
"""
32+
33+
34+
@ports(inputs=["start", "goal"], outputs=["command"])
35+
class MyAsyncNode(AsyncActionNode):
36+
def run(self):
37+
start = np.asarray(self.get_input("start"))
38+
goal = np.asarray(self.get_input("goal"))
39+
40+
for t in np.linspace(0.0, 1.0, num=10):
41+
command = (1.0 - t) * start + t * goal
42+
self.set_output("command", command)
43+
yield
44+
45+
print("Trajectory finished!")
46+
47+
return NodeStatus.SUCCESS
48+
49+
def on_halted(self):
50+
print("Aborted")
51+
52+
53+
@ports(inputs=["value"])
54+
class Print(SyncActionNode):
55+
def tick(self):
56+
value = self.get_input("value")
57+
if value is not None:
58+
print(value)
59+
return NodeStatus.SUCCESS
60+
61+
62+
factory = BehaviorTreeFactory()
63+
factory.register(MyAsyncNode)
64+
factory.register(Print)
65+
66+
tree = factory.create_tree_from_text(xml_text)
67+
tree.tick_while_running()

0 commit comments

Comments
 (0)