mirror of https://github.com/nymea/nymea.git
140 lines
4.6 KiB
C
140 lines
4.6 KiB
C
#ifndef PYPLUGINTIMER_H
|
|
#define PYPLUGINTIMER_H
|
|
|
|
#include <Python.h>
|
|
#include <QTimer>
|
|
#include <QThread>
|
|
#include <QCoreApplication>
|
|
#include <QtConcurrent/QtConcurrent>
|
|
#include "structmember.h"
|
|
|
|
#include "loggingcategories.h"
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
|
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
|
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
|
|
|
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
PyObject *pyTimeoutHandler = nullptr;
|
|
QTimer *timer;
|
|
int interval;
|
|
PyInterpreterState *interpreter;
|
|
} PyPluginTimer;
|
|
|
|
static int PyPluginTimer_init(PyPluginTimer *self, PyObject *args, PyObject */*kwds*/)
|
|
{
|
|
PyObject *handler;
|
|
|
|
qCDebug(dcPythonIntegrations()) << "+++ PyPluginTimer";
|
|
if (!PyArg_ParseTuple(args, "i|O", &self->interval, &handler)) {
|
|
return -1;
|
|
}
|
|
|
|
// QTimer needs to be run in a thread that has a QEventLoop but we don't necessarily have one in
|
|
// python threads. So we're moving the timer to the main app thread.
|
|
self->timer = new QTimer();
|
|
self->timer->start(self->interval * 1000);
|
|
self->timer->moveToThread(QCoreApplication::instance()->thread());
|
|
|
|
self->pyTimeoutHandler = handler;
|
|
Py_XINCREF(handler);
|
|
|
|
// Remember the interpreter from the current thread so we can run the callback in the correct interpreter
|
|
self->interpreter = PyThreadState_GET()->interp;
|
|
|
|
|
|
QObject::connect(self->timer, &QTimer::timeout, [=](){
|
|
qCDebug(dcPythonIntegrations) << "Plugin timer timeout" << self->pyTimeoutHandler;
|
|
|
|
// Spawn a new thread for the callback of the timer (like we do for every python call).
|
|
// FIXME: Ideally we'd use the plugin's thread pool but we can't easily access that here.
|
|
// If the timer callback blocks for longer than the timer interval, we might end up with
|
|
// tons of threads...
|
|
QFuture<void> future = QtConcurrent::run([=](){
|
|
// Acquire GIL and make the new thread state the current one
|
|
PyThreadState *threadState = PyThreadState_New(self->interpreter);
|
|
PyEval_RestoreThread(threadState);
|
|
|
|
if (self->pyTimeoutHandler) {
|
|
|
|
PyObject *ret = PyObject_CallFunction(self->pyTimeoutHandler, nullptr);
|
|
if (PyErr_Occurred()) {
|
|
PyErr_Print();
|
|
}
|
|
Py_XDECREF(ret);
|
|
}
|
|
PyThreadState_Clear(threadState);
|
|
PyEval_ReleaseThread(threadState);
|
|
PyThreadState_Delete(threadState);
|
|
});
|
|
});
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void PyPluginTimer_dealloc(PyPluginTimer * self)
|
|
{
|
|
qCDebug(dcPythonIntegrations()) << "--- PyPluginTimer";
|
|
Py_XDECREF(self->pyTimeoutHandler);
|
|
|
|
QMetaObject::invokeMethod(self->timer, "stop", Qt::QueuedConnection);
|
|
self->timer->deleteLater();
|
|
|
|
Py_TYPE(self)->tp_free(self);
|
|
}
|
|
|
|
|
|
static PyObject *PyPluginTimer_getInterval(PyPluginTimer *self, void */*closure*/)
|
|
{
|
|
return PyLong_FromLong(self->interval);
|
|
}
|
|
|
|
static int PyPluginTimer_setInterval(PyPluginTimer *self, PyObject *value, void */*closure*/){
|
|
self->interval = PyLong_AsLong(value);
|
|
QMetaObject::invokeMethod(self->timer, "start", Qt::QueuedConnection, Q_ARG(int, self->interval * 1000));
|
|
return 0;
|
|
}
|
|
|
|
static PyGetSetDef PyPluginTimer_getset[] = {
|
|
{"interval", (getter)PyPluginTimer_getInterval, (setter)PyPluginTimer_setInterval, "Timer interval", nullptr},
|
|
{nullptr , nullptr, nullptr, nullptr, nullptr} /* Sentinel */
|
|
};
|
|
|
|
|
|
static PyMemberDef PyPluginTimer_members[] = {
|
|
{"timeoutHandler", T_OBJECT_EX, offsetof(PyPluginTimer, pyTimeoutHandler), 0, "Set a callback for when the timer timeout triggers."},
|
|
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
|
|
};
|
|
|
|
static PyTypeObject PyPluginTimerType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
"nymea.PluginTimer", /* tp_name */
|
|
sizeof(PyPluginTimer), /* tp_basicsize */
|
|
0, /* tp_itemsize */
|
|
(destructor)PyPluginTimer_dealloc, /* tp_dealloc */
|
|
};
|
|
|
|
|
|
|
|
static void registerPluginTimerType(PyObject *module)
|
|
{
|
|
PyPluginTimerType.tp_new = PyType_GenericNew;
|
|
PyPluginTimerType.tp_members = PyPluginTimer_members;
|
|
PyPluginTimerType.tp_getset = PyPluginTimer_getset;
|
|
PyPluginTimerType.tp_init = reinterpret_cast<initproc>(PyPluginTimer_init);
|
|
PyPluginTimerType.tp_doc = "PluginTimers can be used to perform repeating tasks, such as polling a device or online service.";
|
|
PyPluginTimerType.tp_flags = Py_TPFLAGS_DEFAULT;
|
|
|
|
if (PyType_Ready(&PyPluginTimerType) < 0) {
|
|
return;
|
|
}
|
|
PyModule_AddObject(module, "PluginTimer", reinterpret_cast<PyObject*>(&PyPluginTimerType));
|
|
}
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
#endif // PYPLUGINTIMER_H
|