forked from aldebaran/libqi-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyasync.cpp
More file actions
160 lines (143 loc) · 5.55 KB
/
pyasync.cpp
File metadata and controls
160 lines (143 loc) · 5.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*
** Copyright (C) 2020 SoftBank Robotics Europe
** See COPYING for the license
*/
#include <qipython/pysignal.hpp>
#include <qipython/common.hpp>
#include <qipython/pyguard.hpp>
#include <qipython/pyfuture.hpp>
#include <qipython/pyobject.hpp>
#include <qipython/pystrand.hpp>
#include <pybind11/pybind11.h>
#include <qi/signal.hpp>
#include <qi/anyobject.hpp>
#include <qi/periodictask.hpp>
namespace py = pybind11;
namespace qi
{
namespace py
{
namespace
{
constexpr const auto delayArgName = "delay";
::py::object async(::py::function pyCallback,
::py::args args,
::py::kwargs kwargs)
{
GILAcquire lock;
qi::uint64_t usDelay = 0;
if (const auto optUsDelay = extractKeywordArg<qi::uint64_t>(kwargs, delayArgName))
usDelay = *optUsDelay;
const MicroSeconds delay(usDelay);
SharedObject sharedCb(pyCallback);
SharedObject sharedArgs(std::move(args));
SharedObject sharedKwArgs(std::move(kwargs));
auto invokeCallback = [=]() mutable {
GILAcquire acquire;
return invokeCatchPythonError(
sharedCb.takeInner(),
*sharedArgs.takeInner(),
**sharedKwArgs.takeInner()).cast<AnyValue>();
};
// If there is a strand attached to that callable, we use it but we cannot use
// asyncDelay (we use defer instead). This is because we might be executing
// this function from inside the strand execution context, and thus asyncDelay
// might be blocking.
Promise prom;
const auto strand = strandOfFunction(pyCallback);
if (strand)
{
strand->defer([=]() mutable { prom.setValue(invokeCallback()); }, delay)
.connect([=](qi::Future<void> fut) mutable {
if (fut.hasValue()) return;
adaptFuture(fut, prom);
});
}
else
adaptFuture(asyncDelay(invokeCallback, delay), prom);
return castToPyObject(prom.future());
}
} // namespace
void exportAsync(::py::module& m)
{
using namespace ::py;
using namespace ::py::literals;
GILAcquire lock;
m.def("runAsync", &async,
"callback"_a,
// TODO: use `::py:kwonly, ::py::arg(delayArgName) = 0` when available.
doc(":param callback: the callback that will be called\n"
":param delay: an optional delay in microseconds\n"
":returns: a future with the return value of the function"));
class_<PeriodicTask>(m, "PeriodicTask")
.def(init<>())
.def(
"setCallback",
[](PeriodicTask& task, ::py::function pyCallback) {
auto callback = pyCallback.cast<std::function<void()>>();
task.setCallback(std::move(callback));
task.setStrand(strandOfFunction(pyCallback).get());
},
"callable"_a,
doc(
"Set the callback used by the periodic task, this function can only be "
"called once.\n"
":param callable: a python callable, could be a method or a function."))
.def("setUsPeriod",
[](PeriodicTask& task, qi::int64_t usPeriod) {
task.setPeriod(qi::MicroSeconds(usPeriod));
},
call_guard<GILRelease>(), "usPeriod"_a,
doc("Set the call interval in microseconds.\n"
"This call will wait until next callback invocation to apply the "
"change.\n"
"To apply the change immediately, use: \n"
"\n"
".. code-block:: python\n"
"\n"
" task.stop()\n"
" task.setUsPeriod(100)\n"
" task.start()\n"
":param usPeriod: the period in microseconds"))
.def("start", &PeriodicTask::start, call_guard<GILRelease>(),
"immediate"_a,
doc("Start the periodic task at specified period. No effect if "
"already running.\n"
":param immediate: immediate if true, first schedule of the task "
"will happen with no delay.\n"
".. warning::\n"
" concurrent calls to start() and stop() will result in "
"undefined behavior."))
.def("stop", &PeriodicTask::stop, call_guard<GILRelease>(),
doc("Stop the periodic task. When this function returns, the callback "
"will not be called "
"anymore. Can be called from within the callback function\n"
".. warning::\n"
" concurrent calls to start() and stop() will result in "
"undefined behavior."))
.def("asyncStop", &PeriodicTask::asyncStop,
call_guard<GILRelease>(),
doc("Request for periodic task to stop asynchronously.\n"
"Can be called from within the callback function."))
.def(
"compensateCallbackTime", &PeriodicTask::compensateCallbackTime,
call_guard<GILRelease>(), "compensate"_a,
doc(
":param compensate: boolean. True to activate the compensation.\n"
"When compensation is activated, call interval will take into account "
"call duration to maintain the period.\n"
".. warning::\n"
" when the callback is longer than the specified period, "
"compensation will result in the callback being called successively "
"without pause."))
.def("setName", &PeriodicTask::setName, call_guard<GILRelease>(),
"name"_a, doc("Set name for debugging and tracking purpose"))
.def("isRunning", &PeriodicTask::isRunning,
doc(":returns: true if task is running"))
.def("isStopping", &PeriodicTask::isStopping,
doc("Can be called from within the callback to know if stop() or "
"asyncStop() was called.\n"
":returns: whether state is stopping or stopped."));
}
} // namespace py
} // namespace qi