Blender V5.0
bpy_app_timers.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_timer.h"
10
11#include <Python.h>
12
13#include <algorithm>
14
15#include "bpy_app_timers.hh"
16
17#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
18
19static double handle_returned_value(PyObject *function, PyObject *ret)
20{
21 if (ret == nullptr) {
22 PyErr_PrintEx(0);
23 return -1;
24 }
25
26 if (ret == Py_None) {
27 return -1;
28 }
29
30 double value = PyFloat_AsDouble(ret);
31 if (value == -1.0f && PyErr_Occurred()) {
32 PyErr_Clear();
33 printf("Error: 'bpy.app.timers' callback ");
34 PyObject_Print(function, stdout, Py_PRINT_RAW);
35 printf(" did not return None or float.\n");
36 return -1;
37 }
38
39 value = std::max(value, 0.0);
40
41 return value;
42}
43
44static double py_timer_execute(uintptr_t /*uuid*/, void *user_data)
45{
46 PyGILState_STATE gilstate = PyGILState_Ensure();
47
48 PyObject *function = static_cast<PyObject *>(user_data);
49
50 PyObject *py_ret = PyObject_CallObject(function, nullptr);
51 const double ret = handle_returned_value(function, py_ret);
52
53 PyGILState_Release(gilstate);
54
55 return ret;
56}
57
58static void py_timer_free(uintptr_t /*uuid*/, void *user_data)
59{
60 PyGILState_STATE gilstate = PyGILState_Ensure();
61
62 PyObject *function = static_cast<PyObject *>(user_data);
63 Py_DECREF(function);
64
65 PyGILState_Release(gilstate);
66}
67
69 /* Wrap. */
70 bpy_app_timers_register_doc,
71 ".. function:: register(function, *, first_interval=0, persistent=False)\n"
72 "\n"
73 " Add a new function that will be called after the specified amount of seconds.\n"
74 " The function gets no arguments and is expected to return either None or a float.\n"
75 " If ``None`` is returned, the timer will be unregistered.\n"
76 " A returned number specifies the delay until the function is called again.\n"
77 " ``functools.partial`` can be used to assign some parameters.\n"
78 "\n"
79 " :arg function: The function that should called.\n"
80 " :type function: Callable[[], float | None]\n"
81 " :arg first_interval: Seconds until the callback should be called the first time.\n"
82 " :type first_interval: float\n"
83 " :arg persistent: Don't remove timer when a new file is loaded.\n"
84 " :type persistent: bool\n");
85static PyObject *bpy_app_timers_register(PyObject * /*self*/, PyObject *args, PyObject *kw)
86{
87 PyObject *function;
88 double first_interval = 0;
89 int persistent = false;
90
91 static const char *_keywords[] = {"function", "first_interval", "persistent", nullptr};
92 static _PyArg_Parser _parser = {
94 "O" /* `function` */
95 "|$" /* Optional keyword only arguments. */
96 "d" /* `first_interval` */
97 "p" /* `persistent` */
98 ":register",
99 _keywords,
100 nullptr,
101 };
102 if (!_PyArg_ParseTupleAndKeywordsFast(
103 args, kw, &_parser, &function, &first_interval, &persistent))
104 {
105 return nullptr;
106 }
107
108 if (!PyCallable_Check(function)) {
109 PyErr_SetString(PyExc_TypeError, "function is not callable");
110 return nullptr;
111 }
112
113 Py_INCREF(function);
115 intptr_t(function), py_timer_execute, function, py_timer_free, first_interval, persistent);
116 Py_RETURN_NONE;
117}
118
120 /* Wrap. */
121 bpy_app_timers_unregister_doc,
122 ".. function:: unregister(function)\n"
123 "\n"
124 " Unregister timer.\n"
125 "\n"
126 " :arg function: Function to unregister.\n"
127 " :type function: Callable[[], float | None]\n");
128static PyObject *bpy_app_timers_unregister(PyObject * /*self*/, PyObject *function)
129{
130 if (!BLI_timer_unregister(intptr_t(function))) {
131 PyErr_SetString(PyExc_ValueError, "function is not registered");
132 return nullptr;
133 }
134 Py_RETURN_NONE;
135}
136
138 /* Wrap. */
139 bpy_app_timers_is_registered_doc,
140 ".. function:: is_registered(function)\n"
141 "\n"
142 " Check if this function is registered as a timer.\n"
143 "\n"
144 " :arg function: Function to check.\n"
145 " :type function: Callable[[], float | None]\n"
146 " :return: True when this function is registered, otherwise False.\n"
147 " :rtype: bool\n");
148static PyObject *bpy_app_timers_is_registered(PyObject * /*self*/, PyObject *function)
149{
150 const bool ret = BLI_timer_is_registered(intptr_t(function));
151 return PyBool_FromLong(ret);
152}
153
154#ifdef __GNUC__
155# ifdef __clang__
156# pragma clang diagnostic push
157# pragma clang diagnostic ignored "-Wcast-function-type"
158# else
159# pragma GCC diagnostic push
160# pragma GCC diagnostic ignored "-Wcast-function-type"
161# endif
162#endif
163
164static PyMethodDef M_AppTimers_methods[] = {
165 {"register",
166 (PyCFunction)bpy_app_timers_register,
167 METH_VARARGS | METH_KEYWORDS,
168 bpy_app_timers_register_doc},
169 {"unregister", (PyCFunction)bpy_app_timers_unregister, METH_O, bpy_app_timers_unregister_doc},
170 {"is_registered",
171 (PyCFunction)bpy_app_timers_is_registered,
172 METH_O,
173 bpy_app_timers_is_registered_doc},
174 {nullptr, nullptr, 0, nullptr},
175};
176
177#ifdef __GNUC__
178# ifdef __clang__
179# pragma clang diagnostic pop
180# else
181# pragma GCC diagnostic pop
182# endif
183#endif
184
185static PyModuleDef M_AppTimers_module_def = {
186 /*m_base*/ PyModuleDef_HEAD_INIT,
187 /*m_name*/ "bpy.app.timers",
188 /*m_doc*/ nullptr,
189 /*m_size*/ 0,
190 /*m_methods*/ M_AppTimers_methods,
191 /*m_slots*/ nullptr,
192 /*m_traverse*/ nullptr,
193 /*m_clear*/ nullptr,
194 /*m_free*/ nullptr,
195};
196
198{
199 PyObject *sys_modules = PyImport_GetModuleDict();
200 PyObject *mod = PyModule_Create(&M_AppTimers_module_def);
201 PyDict_SetItem(sys_modules, PyModule_GetNameObject(mod), mod);
202 return mod;
203}
bool BLI_timer_is_registered(uintptr_t uuid)
Definition BLI_timer.cc:73
void BLI_timer_register(uintptr_t uuid, BLI_timer_func func, void *user_data, BLI_timer_data_free user_data_free, double first_interval, bool persistent)
Definition BLI_timer.cc:34
bool BLI_timer_unregister(uintptr_t uuid)
Definition BLI_timer.cc:61
PyObject * BPY_app_timers_module()
static double handle_returned_value(PyObject *function, PyObject *ret)
static PyObject * bpy_app_timers_unregister(PyObject *, PyObject *function)
static PyObject * bpy_app_timers_register(PyObject *, PyObject *args, PyObject *kw)
static double py_timer_execute(uintptr_t, void *user_data)
static PyModuleDef M_AppTimers_module_def
static PyObject * bpy_app_timers_is_registered(PyObject *, PyObject *function)
static void py_timer_free(uintptr_t, void *user_data)
static PyMethodDef M_AppTimers_methods[]
PyDoc_STRVAR(bpy_app_timers_register_doc, ".. function:: register(function, *, first_interval=0, persistent=False)\n" "\n" " Add a new function that will be called after the specified amount of seconds.\n" " The function gets no arguments and is expected to return either None or a float.\n" " If ``None`` is returned, the timer will be unregistered.\n" " A returned number specifies the delay until the function is called again.\n" " ``functools.partial`` can be used to assign some parameters.\n" "\n" " :arg function: The function that should called.\n" " :type function: Callable[[], float | None]\n" " :arg first_interval: Seconds until the callback should be called the first time.\n" " :type first_interval: float\n" " :arg persistent: Don't remove timer when a new file is loaded.\n" " :type persistent: bool\n")
#define printf(...)
VecBase< float, D > constexpr mod(VecOp< float, D >, VecOp< float, D >) RET
header-only compatibility defines.
Py_DECREF(oname)
#define PY_ARG_PARSER_HEAD_COMPAT()
return ret