@@ -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
0 commit comments