Skip to content

Commit 11f4ed0

Browse files
committed
new: add primitive skill which executes in a separate thread
1 parent 60311bf commit 11f4ed0

File tree

1 file changed

+97
-0
lines changed

1 file changed

+97
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from typing import final
2+
import threading
3+
from skiros2_common.core.primitive import PrimitiveBase
4+
from skiros2_common.core.abstract_skill import State
5+
6+
7+
class PrimitiveThreadBase(PrimitiveBase):
8+
"""
9+
@brief Base class for primitive skill that executes a long running
10+
process
11+
12+
A primitive skill must not block the ticking of the BT. This
13+
class is intended for executing a long running process that
14+
cannot be reasonably split up into smaller pieces and instead
15+
starts a thread which executes the long running process.
16+
17+
In particular this class allows the SkiROS gui to provide
18+
feedback and allows other skills to run at the same time as
19+
this skill is executing.
20+
"""
21+
22+
def preStart(self):
23+
"""
24+
@brief Any preparation is performed here
25+
26+
Runs before the thread is started and should do any
27+
(small) preparations that need to be done before starting
28+
the skill. The result of this function is then passed
29+
through and returned by onStart.
30+
31+
@return True if the thread should start, False if not.
32+
"""
33+
return True
34+
35+
@final
36+
def onStart(self):
37+
"""
38+
@brief Starts the main thread of the skill
39+
"""
40+
# Reset relevant flags
41+
self.status = 'Running.'
42+
self.caught_exception = False
43+
self.skill_succeeded = False
44+
self._skill_state = State.Running
45+
46+
# Perform any precomputations in this function
47+
skill_can_start = self.preStart()
48+
49+
# Start new thread if preStart was successful
50+
if skill_can_start:
51+
self.thread = threading.Thread(target=self._run)
52+
self.thread.excepthook = self._excepthook
53+
self.thread.start()
54+
55+
return skill_can_start
56+
57+
def _excepthook(self, args):
58+
self.caught_exception = True
59+
self.exception_msg = args
60+
61+
@final
62+
def execute(self):
63+
if self.thread.is_alive():
64+
# While the skill is not complete return the running message
65+
return self.step(self.status)
66+
else:
67+
self.thread.join()
68+
69+
if not self.caught_exception:
70+
# Return a complete message
71+
return self._skill_state
72+
73+
# Send any exception to the skiros gui.
74+
return self.fail(self.exception_msg, -1)
75+
76+
@final
77+
def _run(self):
78+
self._skill_state = self.run()
79+
if self._skill_state == State.Running:
80+
raise RuntimeError('The run function should not return a non terminal state.')
81+
82+
def run(self):
83+
"""
84+
@brief Performs the long running execution which must happen
85+
in a separate thread
86+
87+
This function must not return a non-terminal state, i.e.
88+
`self.step('...')` cannot be returned.
89+
90+
There is no mechanism for preemption, it is responsibility
91+
of whoever implements this function to make sure that the
92+
thread stops when a preemption signal is sent.
93+
94+
@return A SkiROS state, i.e. `self.success('...')` or
95+
`self.fail('...', code)`.
96+
"""
97+
raise NotImplementedError

0 commit comments

Comments
 (0)