PyAPI: add utilities PyTuple_SET_ITEMS, Py_INCREF_RET
[blender.git] / source / blender / python / intern / bpy_app_handlers.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s): Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/python/intern/bpy_app_handlers.c
24  *  \ingroup pythonintern
25  *
26  * This file defines a 'PyStructSequence' accessed via 'bpy.app.handlers',
27  * which exposes various lists that the script author can add callback
28  * functions into (called via blenders generic BLI_cb api)
29  */
30
31 #include <Python.h>
32 #include "BLI_utildefines.h"
33 #include "BLI_callbacks.h"
34
35 #include "RNA_types.h"
36 #include "RNA_access.h"
37 #include "bpy_rna.h"
38 #include "bpy_app_handlers.h"
39
40 #include "../generic/python_utildefines.h"
41
42 #include "BPY_extern.h"
43
44 void bpy_app_generic_callback(struct Main *main, struct ID *id, void *arg);
45
46 static PyTypeObject BlenderAppCbType;
47
48 static PyStructSequence_Field app_cb_info_fields[] = {
49         {(char *)"frame_change_pre",  (char *)"on frame change for playback and rendering (before)"},
50         {(char *)"frame_change_post", (char *)"on frame change for playback and rendering (after)"},
51         {(char *)"render_pre",        (char *)"on render (before)"},
52         {(char *)"render_post",       (char *)"on render (after)"},
53         {(char *)"render_write",      (char *)"on writing a render frame (directly after the frame is written)"},
54         {(char *)"render_stats",      (char *)"on printing render statistics"},
55         {(char *)"render_init",       (char *)"on initialization of a render job"},
56         {(char *)"render_complete",   (char *)"on completion of render job"},
57         {(char *)"render_cancel",     (char *)"on canceling a render job"},
58         {(char *)"load_pre",          (char *)"on loading a new blend file (before)"},
59         {(char *)"load_post",         (char *)"on loading a new blend file (after)"},
60         {(char *)"save_pre",          (char *)"on saving a blend file (before)"},
61         {(char *)"save_post",         (char *)"on saving a blend file (after)"},
62         {(char *)"scene_update_pre",  (char *)"on updating the scenes data (before)"},
63         {(char *)"scene_update_post", (char *)"on updating the scenes data (after)"},
64         {(char *)"game_pre",          (char *)"on starting the game engine"},
65         {(char *)"game_post",         (char *)"on ending the game engine"},
66         {(char *)"version_update",    (char *)"on ending the versioning code"},
67
68         /* sets the permanent tag */
69 #   define APP_CB_OTHER_FIELDS 1
70         {(char *)"persistent",        (char *)"Function decorator for callback functions not to be removed when loading new files"},
71
72         {NULL}
73 };
74
75 static PyStructSequence_Desc app_cb_info_desc = {
76         (char *)"bpy.app.handlers",     /* name */
77         (char *)"This module contains callback lists",  /* doc */
78         app_cb_info_fields,    /* fields */
79         ARRAY_SIZE(app_cb_info_fields) - 1
80 };
81
82 #if 0
83 #  if (BLI_CB_EVT_TOT != ARRAY_SIZE(app_cb_info_fields))
84 #    error "Callbacks are out of sync"
85 #  endif
86 #endif
87
88 /* --------------------------------------------------------------------------*/
89 /* permanent tagging code */
90 #define PERMINENT_CB_ID "_bpy_persistent"
91
92 static PyObject *bpy_app_handlers_persistent_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *UNUSED(kwds))
93 {
94         PyObject *value;
95
96         if (!PyArg_ParseTuple(args, "O:bpy.app.handlers.persistent", &value))
97                 return NULL;
98
99         if (PyFunction_Check(value)) {
100                 PyObject **dict_ptr = _PyObject_GetDictPtr(value);
101                 if (dict_ptr == NULL) {
102                         PyErr_SetString(PyExc_ValueError,
103                                         "bpy.app.handlers.persistent wasn't able to "
104                                         "get the dictionary from the function passed");
105                         return NULL;
106                 }
107                 else {
108                         /* set id */
109                         if (*dict_ptr == NULL) {
110                                 *dict_ptr = PyDict_New();
111                         }
112
113                         PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None);
114                 }
115
116                 Py_INCREF(value);
117                 return value;
118         }
119         else {
120                 PyErr_SetString(PyExc_ValueError,
121                                 "bpy.app.handlers.persistent expected a function");
122                 return NULL;
123         }
124 }
125
126 /* dummy type because decorators can't be PyCFunctions */
127 static PyTypeObject BPyPersistent_Type = {
128
129 #if defined(_MSC_VER) || defined(FREE_WINDOWS)
130         PyVarObject_HEAD_INIT(NULL, 0)
131 #else
132         PyVarObject_HEAD_INIT(&PyType_Type, 0)
133 #endif
134
135         "persistent",                               /* tp_name */
136         0,                                          /* tp_basicsize */
137         0,                                          /* tp_itemsize */
138         /* methods */
139         0,                                          /* tp_dealloc */
140         0,                                          /* tp_print */
141         0,                                          /* tp_getattr */
142         0,                                          /* tp_setattr */
143         0,                                          /* tp_reserved */
144         0,                                          /* tp_repr */
145         0,                                          /* tp_as_number */
146         0,                                          /* tp_as_sequence */
147         0,                                          /* tp_as_mapping */
148         0,                                          /* tp_hash */
149         0,                                          /* tp_call */
150         0,                                          /* tp_str */
151         0,                                          /* tp_getattro */
152         0,                                          /* tp_setattro */
153         0,                                          /* tp_as_buffer */
154         Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
155         Py_TPFLAGS_BASETYPE,                        /* tp_flags */
156         0,                                          /* tp_doc */
157         0,                                          /* tp_traverse */
158         0,                                          /* tp_clear */
159         0,                                          /* tp_richcompare */
160         0,                                          /* tp_weaklistoffset */
161         0,                                          /* tp_iter */
162         0,                                          /* tp_iternext */
163         0,                                          /* tp_methods */
164         0,                                          /* tp_members */
165         0,                                          /* tp_getset */
166         0,                                          /* tp_base */
167         0,                                          /* tp_dict */
168         0,                                          /* tp_descr_get */
169         0,                                          /* tp_descr_set */
170         0,                                          /* tp_dictoffset */
171         0,                                          /* tp_init */
172         0,                                          /* tp_alloc */
173         bpy_app_handlers_persistent_new,            /* tp_new */
174         0,                                          /* tp_free */
175 };
176
177 static PyObject *py_cb_array[BLI_CB_EVT_TOT] = {NULL};
178
179 static PyObject *make_app_cb_info(void)
180 {
181         PyObject *app_cb_info;
182         int pos;
183
184         app_cb_info = PyStructSequence_New(&BlenderAppCbType);
185         if (app_cb_info == NULL) {
186                 return NULL;
187         }
188
189         for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
190                 if (app_cb_info_fields[pos].name == NULL) {
191                         Py_FatalError("invalid callback slots 1");
192                 }
193                 PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos] = PyList_New(0)));
194         }
195         if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != NULL) {
196                 Py_FatalError("invalid callback slots 2");
197         }
198
199         /* custom function */
200         PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)&BPyPersistent_Type);
201
202         return app_cb_info;
203 }
204
205 PyObject *BPY_app_handlers_struct(void)
206 {
207         PyObject *ret;
208
209 #if defined(_MSC_VER) || defined(FREE_WINDOWS)
210         BPyPersistent_Type.ob_base.ob_base.ob_type = &PyType_Type;
211 #endif
212
213         if (PyType_Ready(&BPyPersistent_Type) < 0) {
214                 BLI_assert(!"error initializing 'bpy.app.handlers.persistent'");
215         }
216
217         PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc);
218
219         ret = make_app_cb_info();
220
221         /* prevent user from creating new instances */
222         BlenderAppCbType.tp_init = NULL;
223         BlenderAppCbType.tp_new = NULL;
224         BlenderAppCbType.tp_hash = (hashfunc)_Py_HashPointer; /* without this we can't do set(sys.modules) [#29635] */
225
226         /* assign the C callbacks */
227         if (ret) {
228                 static bCallbackFuncStore funcstore_array[BLI_CB_EVT_TOT] = {{NULL}};
229                 bCallbackFuncStore *funcstore;
230                 int pos = 0;
231
232                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
233                         funcstore = &funcstore_array[pos];
234                         funcstore->func = bpy_app_generic_callback;
235                         funcstore->alloc = 0;
236                         funcstore->arg = SET_INT_IN_POINTER(pos);
237                         BLI_callback_add(funcstore, pos);
238                 }
239         }
240
241         return ret;
242 }
243
244 void BPY_app_handlers_reset(const short do_all)
245 {
246         PyGILState_STATE gilstate;
247         int pos = 0;
248
249         gilstate = PyGILState_Ensure();
250
251         if (do_all) {
252                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
253                         /* clear list */
254                         PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL);
255                 }
256         }
257         else {
258                 /* save string conversion thrashing */
259                 PyObject *perm_id_str = PyUnicode_FromString(PERMINENT_CB_ID);
260
261                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
262                         /* clear only items without PERMINENT_CB_ID */
263                         PyObject *ls = py_cb_array[pos];
264                         Py_ssize_t i;
265
266                         PyObject *item;
267                         PyObject **dict_ptr;
268
269                         for (i = PyList_GET_SIZE(ls) - 1; i >= 0; i--) {
270
271                                 if ((PyFunction_Check((item = PyList_GET_ITEM(ls, i)))) &&
272                                     (dict_ptr = _PyObject_GetDictPtr(item)) &&
273                                     (*dict_ptr) &&
274                                     (PyDict_GetItem(*dict_ptr, perm_id_str) != NULL))
275                                 {
276                                         /* keep */
277                                 }
278                                 else {
279                                         /* remove */
280                                         /* PySequence_DelItem(ls, i); */ /* more obvious buw slower */
281                                         PyList_SetSlice(ls, i, i + 1, NULL);
282                                 }
283                         }
284                 }
285
286                 Py_DECREF(perm_id_str);
287         }
288
289         PyGILState_Release(gilstate);
290 }
291
292 /* the actual callback - not necessarily called from py */
293 void bpy_app_generic_callback(struct Main *UNUSED(main), struct ID *id, void *arg)
294 {
295         PyObject *cb_list = py_cb_array[GET_INT_FROM_POINTER(arg)];
296         if (PyList_GET_SIZE(cb_list) > 0) {
297                 PyGILState_STATE gilstate = PyGILState_Ensure();
298
299                 PyObject *args = PyTuple_New(1);  /* save python creating each call */
300                 PyObject *func;
301                 PyObject *ret;
302                 Py_ssize_t pos;
303
304                 /* setup arguments */
305                 if (id) {
306                         PointerRNA id_ptr;
307                         RNA_id_pointer_create(id, &id_ptr);
308                         PyTuple_SET_ITEM(args, 0, pyrna_struct_CreatePyObject(&id_ptr));
309                 }
310                 else {
311                         PyTuple_SET_ITEM(args, 0, Py_INCREF_RET(Py_None));
312                 }
313
314                 /* Iterate the list and run the callbacks
315                  * note: don't store the list size since the scripts may remove themselves */
316                 for (pos = 0; pos < PyList_GET_SIZE(cb_list); pos++) {
317                         func = PyList_GET_ITEM(cb_list, pos);
318                         ret = PyObject_Call(func, args, NULL);
319                         if (ret == NULL) {
320                                 PyErr_Print();
321                                 PyErr_Clear();
322                         }
323                         else {
324                                 Py_DECREF(ret);
325                         }
326                 }
327
328                 Py_DECREF(args);
329
330                 PyGILState_Release(gilstate);
331         }
332 }