Fix T55945: Outliner crash
[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 *)"version_update",    (char *)"on ending the versioning code"},
63
64         /* sets the permanent tag */
65 #   define APP_CB_OTHER_FIELDS 1
66         {(char *)"persistent",        (char *)"Function decorator for callback functions not to be removed when loading new files"},
67
68         {NULL}
69 };
70
71 static PyStructSequence_Desc app_cb_info_desc = {
72         (char *)"bpy.app.handlers",     /* name */
73         (char *)"This module contains callback lists",  /* doc */
74         app_cb_info_fields,    /* fields */
75         ARRAY_SIZE(app_cb_info_fields) - 1
76 };
77
78 #if 0
79 #  if (BLI_CB_EVT_TOT != ARRAY_SIZE(app_cb_info_fields))
80 #    error "Callbacks are out of sync"
81 #  endif
82 #endif
83
84 /* --------------------------------------------------------------------------*/
85 /* permanent tagging code */
86 #define PERMINENT_CB_ID "_bpy_persistent"
87
88 static PyObject *bpy_app_handlers_persistent_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *UNUSED(kwds))
89 {
90         PyObject *value;
91
92         if (!PyArg_ParseTuple(args, "O:bpy.app.handlers.persistent", &value))
93                 return NULL;
94
95         if (PyFunction_Check(value)) {
96                 PyObject **dict_ptr = _PyObject_GetDictPtr(value);
97                 if (dict_ptr == NULL) {
98                         PyErr_SetString(PyExc_ValueError,
99                                         "bpy.app.handlers.persistent wasn't able to "
100                                         "get the dictionary from the function passed");
101                         return NULL;
102                 }
103                 else {
104                         /* set id */
105                         if (*dict_ptr == NULL) {
106                                 *dict_ptr = PyDict_New();
107                         }
108
109                         PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None);
110                 }
111
112                 Py_INCREF(value);
113                 return value;
114         }
115         else {
116                 PyErr_SetString(PyExc_ValueError,
117                                 "bpy.app.handlers.persistent expected a function");
118                 return NULL;
119         }
120 }
121
122 /* dummy type because decorators can't be PyCFunctions */
123 static PyTypeObject BPyPersistent_Type = {
124
125 #if defined(_MSC_VER)
126         PyVarObject_HEAD_INIT(NULL, 0)
127 #else
128         PyVarObject_HEAD_INIT(&PyType_Type, 0)
129 #endif
130
131         "persistent",                               /* tp_name */
132         0,                                          /* tp_basicsize */
133         0,                                          /* tp_itemsize */
134         /* methods */
135         0,                                          /* tp_dealloc */
136         0,                                          /* tp_print */
137         0,                                          /* tp_getattr */
138         0,                                          /* tp_setattr */
139         0,                                          /* tp_reserved */
140         0,                                          /* tp_repr */
141         0,                                          /* tp_as_number */
142         0,                                          /* tp_as_sequence */
143         0,                                          /* tp_as_mapping */
144         0,                                          /* tp_hash */
145         0,                                          /* tp_call */
146         0,                                          /* tp_str */
147         0,                                          /* tp_getattro */
148         0,                                          /* tp_setattro */
149         0,                                          /* tp_as_buffer */
150         Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
151         Py_TPFLAGS_BASETYPE,                        /* tp_flags */
152         0,                                          /* tp_doc */
153         0,                                          /* tp_traverse */
154         0,                                          /* tp_clear */
155         0,                                          /* tp_richcompare */
156         0,                                          /* tp_weaklistoffset */
157         0,                                          /* tp_iter */
158         0,                                          /* tp_iternext */
159         0,                                          /* tp_methods */
160         0,                                          /* tp_members */
161         0,                                          /* tp_getset */
162         0,                                          /* tp_base */
163         0,                                          /* tp_dict */
164         0,                                          /* tp_descr_get */
165         0,                                          /* tp_descr_set */
166         0,                                          /* tp_dictoffset */
167         0,                                          /* tp_init */
168         0,                                          /* tp_alloc */
169         bpy_app_handlers_persistent_new,            /* tp_new */
170         0,                                          /* tp_free */
171 };
172
173 static PyObject *py_cb_array[BLI_CB_EVT_TOT] = {NULL};
174
175 static PyObject *make_app_cb_info(void)
176 {
177         PyObject *app_cb_info;
178         int pos;
179
180         app_cb_info = PyStructSequence_New(&BlenderAppCbType);
181         if (app_cb_info == NULL) {
182                 return NULL;
183         }
184
185         for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
186                 if (app_cb_info_fields[pos].name == NULL) {
187                         Py_FatalError("invalid callback slots 1");
188                 }
189                 PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos] = PyList_New(0)));
190         }
191         if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != NULL) {
192                 Py_FatalError("invalid callback slots 2");
193         }
194
195         /* custom function */
196         PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)&BPyPersistent_Type);
197
198         return app_cb_info;
199 }
200
201 PyObject *BPY_app_handlers_struct(void)
202 {
203         PyObject *ret;
204
205 #if defined(_MSC_VER)
206         BPyPersistent_Type.ob_base.ob_base.ob_type = &PyType_Type;
207 #endif
208
209         if (PyType_Ready(&BPyPersistent_Type) < 0) {
210                 BLI_assert(!"error initializing 'bpy.app.handlers.persistent'");
211         }
212
213         PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc);
214
215         ret = make_app_cb_info();
216
217         /* prevent user from creating new instances */
218         BlenderAppCbType.tp_init = NULL;
219         BlenderAppCbType.tp_new = NULL;
220         BlenderAppCbType.tp_hash = (hashfunc)_Py_HashPointer; /* without this we can't do set(sys.modules) [#29635] */
221
222         /* assign the C callbacks */
223         if (ret) {
224                 static bCallbackFuncStore funcstore_array[BLI_CB_EVT_TOT] = {{NULL}};
225                 bCallbackFuncStore *funcstore;
226                 int pos = 0;
227
228                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
229                         funcstore = &funcstore_array[pos];
230                         funcstore->func = bpy_app_generic_callback;
231                         funcstore->alloc = 0;
232                         funcstore->arg = SET_INT_IN_POINTER(pos);
233                         BLI_callback_add(funcstore, pos);
234                 }
235         }
236
237         return ret;
238 }
239
240 void BPY_app_handlers_reset(const short do_all)
241 {
242         PyGILState_STATE gilstate;
243         int pos = 0;
244
245         gilstate = PyGILState_Ensure();
246
247         if (do_all) {
248                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
249                         /* clear list */
250                         PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL);
251                 }
252         }
253         else {
254                 /* save string conversion thrashing */
255                 PyObject *perm_id_str = PyUnicode_FromString(PERMINENT_CB_ID);
256
257                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
258                         /* clear only items without PERMINENT_CB_ID */
259                         PyObject *ls = py_cb_array[pos];
260                         Py_ssize_t i;
261
262                         PyObject *item;
263                         PyObject **dict_ptr;
264
265                         for (i = PyList_GET_SIZE(ls) - 1; i >= 0; i--) {
266
267                                 if ((PyFunction_Check((item = PyList_GET_ITEM(ls, i)))) &&
268                                     (dict_ptr = _PyObject_GetDictPtr(item)) &&
269                                     (*dict_ptr) &&
270                                     (PyDict_GetItem(*dict_ptr, perm_id_str) != NULL))
271                                 {
272                                         /* keep */
273                                 }
274                                 else {
275                                         /* remove */
276                                         /* PySequence_DelItem(ls, i); */ /* more obvious buw slower */
277                                         PyList_SetSlice(ls, i, i + 1, NULL);
278                                 }
279                         }
280                 }
281
282                 Py_DECREF(perm_id_str);
283         }
284
285         PyGILState_Release(gilstate);
286 }
287
288 /* the actual callback - not necessarily called from py */
289 void bpy_app_generic_callback(struct Main *UNUSED(main), struct ID *id, void *arg)
290 {
291         PyObject *cb_list = py_cb_array[GET_INT_FROM_POINTER(arg)];
292         if (PyList_GET_SIZE(cb_list) > 0) {
293                 PyGILState_STATE gilstate = PyGILState_Ensure();
294
295                 PyObject *args = PyTuple_New(1);  /* save python creating each call */
296                 PyObject *func;
297                 PyObject *ret;
298                 Py_ssize_t pos;
299
300                 /* setup arguments */
301                 if (id) {
302                         PointerRNA id_ptr;
303                         RNA_id_pointer_create(id, &id_ptr);
304                         PyTuple_SET_ITEM(args, 0, pyrna_struct_CreatePyObject(&id_ptr));
305                 }
306                 else {
307                         PyTuple_SET_ITEM(args, 0, Py_INCREF_RET(Py_None));
308                 }
309
310                 /* Iterate the list and run the callbacks
311                  * note: don't store the list size since the scripts may remove themselves */
312                 for (pos = 0; pos < PyList_GET_SIZE(cb_list); pos++) {
313                         func = PyList_GET_ITEM(cb_list, pos);
314                         ret = PyObject_Call(func, args, NULL);
315                         if (ret == NULL) {
316                                 /* Don't set last system variables because they might cause some
317                                  * dangling pointers to external render engines (when exception
318                                  * happens during rendering) which will break logic of render pipeline
319                                  * which expects to be the only user of render engine when rendering
320                                  * is finished.
321                                  */
322                                 PyErr_PrintEx(0);
323                                 PyErr_Clear();
324                         }
325                         else {
326                                 Py_DECREF(ret);
327                         }
328                 }
329
330                 Py_DECREF(args);
331
332                 PyGILState_Release(gilstate);
333         }
334 }