diff options
author | Jacques Lucke <mail@jlucke.com> | 2018-11-26 22:25:15 +0300 |
---|---|---|
committer | Jacques Lucke <mail@jlucke.com> | 2018-11-26 22:25:15 +0300 |
commit | c1adf938e6c8ecaec805f4cf95c73480de1bf980 (patch) | |
tree | 2e097af6b8bbf8383463b0d4f54ad011321177ff | |
parent | d5778b5bc1663b14449c90d4bc0e84d56f64c156 (diff) |
Timer: Generic BLI_timer with Python wrapper
There is a new `bpy.app.timers` api.
For more details, look in the Python API documentation.
Reviewers: campbellbarton
Differential Revision: https://developer.blender.org/D3994
-rw-r--r-- | doc/python_api/sphinx_doc_gen.py | 1 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_timer.h | 58 | ||||
-rw-r--r-- | source/blender/blenlib/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/blenlib/intern/BLI_timer.c | 186 | ||||
-rw-r--r-- | source/blender/python/intern/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_app.c | 4 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_app_timers.c | 199 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_app_timers.h | 30 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_event_system.c | 3 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_init_exit.c | 3 |
10 files changed, 488 insertions, 0 deletions
diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index 299eebfe295..ac96ddd8635 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -1842,6 +1842,7 @@ def write_rst_importable_modules(basepath): "bpy.app.handlers": "Application Handlers", "bpy.app.translations": "Application Translations", "bpy.app.icons": "Application Icons", + "bpy.app.timers": "Application Timers", "bpy.props": "Property Definitions", "idprop.types": "ID Property Access", "mathutils": "Math Types & Utilities", diff --git a/source/blender/blenlib/BLI_timer.h b/source/blender/blenlib/BLI_timer.h new file mode 100644 index 00000000000..6dfb13fd738 --- /dev/null +++ b/source/blender/blenlib/BLI_timer.h @@ -0,0 +1,58 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation. + * All rights reserved. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#ifndef __BLI_TIMER_H__ +#define __BLI_TIMER_H__ + +#include "BLI_utildefines.h" + +/** \file BLI_timer.h + * \ingroup BLI + */ + +/* ret < 0: the timer will be removed. + * ret >= 0: the timer will be called again in ret seconds */ +typedef double (*BLI_timer_func)(uintptr_t uuid, void *user_data); +typedef void (*BLI_timer_data_free)(uintptr_t uuid, void *user_data); + +/* `func(...) < 0`: The timer will be removed. + * `func(...) >= 0`: The function will be called again in that many seconds. */ +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); + +bool BLI_timer_is_registered(uintptr_t uuid); + +/* Returns False when the timer does not exist (anymore). */ +bool BLI_timer_unregister(uintptr_t uuid); + +/* Execute all registered functions that are due. */ +void BLI_timer_execute(void); + +void BLI_timer_free(void); + +#endif /* __BLI_TIMER_H__ */ diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 5e6764a5c0e..91887c1ef5e 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -55,6 +55,7 @@ set(SRC intern/BLI_memarena.c intern/BLI_memiter.c intern/BLI_mempool.c + intern/BLI_timer.c intern/DLRB_tree.c intern/array_store.c intern/array_store_utils.c @@ -214,6 +215,7 @@ set(SRC BLI_task.h BLI_threads.h BLI_timecode.h + BLI_timer.h BLI_utildefines.h BLI_utildefines_iter.h BLI_utildefines_stack.h diff --git a/source/blender/blenlib/intern/BLI_timer.c b/source/blender/blenlib/intern/BLI_timer.c new file mode 100644 index 00000000000..875b667de46 --- /dev/null +++ b/source/blender/blenlib/intern/BLI_timer.c @@ -0,0 +1,186 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file, with exception of below: + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/blenlib/intern/BLI_timer.c + * \ingroup bli + */ + +#include "BLI_timer.h" +#include "BLI_listbase.h" +#include "BLI_callbacks.h" + +#include "MEM_guardedalloc.h" +#include "PIL_time.h" + +#define GET_TIME() PIL_check_seconds_timer() + +typedef struct TimedFunction { + struct TimedFunction *next, *prev; + BLI_timer_func func; + BLI_timer_data_free user_data_free; + void *user_data; + double next_time; + uintptr_t uuid; + bool tag_removal; + bool persistent; +} TimedFunction; + +typedef struct TimerContainer { + ListBase funcs; + bool file_load_cb_registered; +} TimerContainer; + +static TimerContainer GlobalTimer = { 0 }; + +static void ensure_callback_is_registered(void); + +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) +{ + ensure_callback_is_registered(); + + TimedFunction *timed_func = MEM_callocN(sizeof(TimedFunction), __func__); + timed_func->func = func; + timed_func->user_data_free = user_data_free; + timed_func->user_data = user_data; + timed_func->next_time = GET_TIME() + first_interval; + timed_func->tag_removal = false; + timed_func->persistent = persistent; + timed_func->uuid = uuid; + + BLI_addtail(&GlobalTimer.funcs, timed_func); +} + +static void clear_user_data(TimedFunction *timed_func) +{ + if (timed_func->user_data_free) { + timed_func->user_data_free(timed_func->uuid, timed_func->user_data); + timed_func->user_data_free = NULL; + } +} + +bool BLI_timer_unregister(uintptr_t uuid) +{ + LISTBASE_FOREACH(TimedFunction *, timed_func, &GlobalTimer.funcs) { + if (timed_func->uuid == uuid) { + if (timed_func->tag_removal) { + return false; + } + else { + timed_func->tag_removal = true; + clear_user_data(timed_func); + return true; + } + } + } + return false; +} + +bool BLI_timer_is_registered(uintptr_t uuid) +{ + LISTBASE_FOREACH(TimedFunction *, timed_func, &GlobalTimer.funcs) { + if (timed_func->uuid == uuid && !timed_func->tag_removal) { + return true; + } + } + return false; +} + +static void execute_functions_if_necessary(void) +{ + double current_time = GET_TIME(); + + LISTBASE_FOREACH(TimedFunction *, timed_func, &GlobalTimer.funcs) { + if (timed_func->tag_removal) continue; + if (timed_func->next_time > current_time) continue; + + double ret = timed_func->func(timed_func->uuid, timed_func->user_data); + + if (ret < 0) { + timed_func->tag_removal = true; + } + else { + timed_func->next_time = current_time + ret; + } + } +} + +static void remove_tagged_functions(void) +{ + for (TimedFunction *timed_func = GlobalTimer.funcs.first; timed_func; ) { + TimedFunction *next = timed_func->next; + if (timed_func->tag_removal) { + clear_user_data(timed_func); + BLI_freelinkN(&GlobalTimer.funcs, timed_func); + } + timed_func = next; + } +} + +void BLI_timer_execute() +{ + execute_functions_if_necessary(); + remove_tagged_functions(); +} + +void BLI_timer_free() +{ + LISTBASE_FOREACH(TimedFunction *, timed_func, &GlobalTimer.funcs) { + timed_func->tag_removal = true; + } + + remove_tagged_functions(); +} + +struct Main; +struct ID; +static void remove_non_persistent_functions(struct Main *UNUSED(_1), struct ID *UNUSED(_2), void *UNUSED(_3)) +{ + LISTBASE_FOREACH(TimedFunction *, timed_func, &GlobalTimer.funcs) { + if (!timed_func->persistent) { + timed_func->tag_removal = true; + } + } +} + +static bCallbackFuncStore load_post_callback = { + NULL, NULL, /* next, prev */ + remove_non_persistent_functions, /* func */ + NULL, /* arg */ + 0 /* alloc */ +}; + +static void ensure_callback_is_registered() +{ + if (!GlobalTimer.file_load_cb_registered) { + BLI_callback_add(&load_post_callback, BLI_CB_EVT_LOAD_POST); + GlobalTimer.file_load_cb_registered = true; + } +} diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index b561504505b..bd7306cddf2 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -58,6 +58,7 @@ set(SRC bpy_app_opensubdiv.c bpy_app_openvdb.c bpy_app_sdl.c + bpy_app_timers.c bpy_app_translations.c bpy_capi_utils.c bpy_driver.c @@ -96,6 +97,7 @@ set(SRC bpy_app_opensubdiv.h bpy_app_openvdb.h bpy_app_sdl.h + bpy_app_timers.h bpy_app_translations.h bpy_capi_utils.h bpy_driver.h diff --git a/source/blender/python/intern/bpy_app.c b/source/blender/python/intern/bpy_app.c index d8c74bdf565..bba9eee0316 100644 --- a/source/blender/python/intern/bpy_app.c +++ b/source/blender/python/intern/bpy_app.c @@ -49,6 +49,7 @@ /* modules */ #include "bpy_app_icons.h" +#include "bpy_app_timers.h" #include "BLI_utildefines.h" @@ -124,6 +125,7 @@ static PyStructSequence_Field app_info_fields[] = { /* Modules (not struct sequence). */ {(char *)"icons", (char *)"Manage custom icons"}, + {(char *)"timers", (char *)"Manage timers"}, {NULL}, }; @@ -137,6 +139,7 @@ PyDoc_STRVAR(bpy_app_doc, "\n" " bpy.app.handlers.rst\n" " bpy.app.icons.rst\n" +" bpy.app.timers.rst\n" " bpy.app.translations.rst\n" ); @@ -220,6 +223,7 @@ static PyObject *make_app_info(void) /* modules */ SetObjItem(BPY_app_icons_module()); + SetObjItem(BPY_app_timers_module()); #undef SetIntItem #undef SetStrItem diff --git a/source/blender/python/intern/bpy_app_timers.c b/source/blender/python/intern/bpy_app_timers.c new file mode 100644 index 00000000000..cd0e57200ec --- /dev/null +++ b/source/blender/python/intern/bpy_app_timers.c @@ -0,0 +1,199 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_app_timers.c + * \ingroup pythonintern + */ + +#include <Python.h> +#include "BLI_utildefines.h" +#include "BLI_timer.h" +#include "PIL_time.h" + +#include "BPY_extern.h" +#include "bpy_app_timers.h" + +#include "../generic/py_capi_utils.h" +#include "../generic/python_utildefines.h" + + +static double handle_returned_value(PyObject *function, PyObject *ret) +{ + if (ret == NULL) { + PyErr_PrintEx(0); + PyErr_Clear(); + return -1; + } + + if (ret == Py_None) { + return -1; + } + + double value = PyFloat_AsDouble(ret); + if (value == -1.0f && PyErr_Occurred()) { + PyErr_Clear(); + printf("Error: 'bpy.app.timers' callback "); + PyObject_Print(function, stdout, Py_PRINT_RAW); + printf(" did not return None or float.\n"); + return -1; + } + + if (value < 0.0) { + value = 0.0; + } + + return value; +} + +static double py_timer_execute(uintptr_t UNUSED(uuid), void *user_data) +{ + PyObject *function = user_data; + + PyGILState_STATE gilstate; + gilstate = PyGILState_Ensure(); + + PyObject *py_ret = PyObject_CallObject(function, NULL); + double ret = handle_returned_value(function, py_ret); + + PyGILState_Release(gilstate); + + return ret; +} + +static void py_timer_free(uintptr_t UNUSED(uuid), void *user_data) +{ + PyObject *function = user_data; + + PyGILState_STATE gilstate; + gilstate = PyGILState_Ensure(); + + Py_DECREF(function); + + PyGILState_Release(gilstate); +} + + +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[[], Union[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" +); +static PyObject *bpy_app_timers_register(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + PyObject *function; + double first_interval = 0; + int persistent = false; + + static const char *_keywords[] = {"function", "first_interval", "persistent", NULL}; + static _PyArg_Parser _parser = {"O|$dp:register", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + &function, &first_interval, &persistent)) + { + return NULL; + } + + if (!PyCallable_Check(function)) { + PyErr_SetString(PyExc_TypeError, "function is not callable"); + return NULL; + } + + Py_INCREF(function); + BLI_timer_register( + (intptr_t)function, + py_timer_execute, function, py_timer_free, + first_interval, persistent); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(bpy_app_timers_unregister_doc, +".. function:: unregister(function)\n" +"\n" +" Unregister timer.\n" +"\n" +" :arg function: Function to unregister.\n" +" :type function: function\n" +); +static PyObject *bpy_app_timers_unregister(PyObject *UNUSED(self), PyObject *function) +{ + if (!BLI_timer_unregister((intptr_t)function)) { + PyErr_SetString(PyExc_ValueError, "Error: function is not registered"); + return NULL; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(bpy_app_timers_is_registered_doc, +".. function:: is_registered(function)\n" +"\n" +" Check if this function is registered as a timer.\n" +"\n" +" :arg function: Function to check.\n" +" :type function: int\n" +" :return: True when this function is registered, otherwise False.\n" +" :rtype: bool\n" +); +static PyObject *bpy_app_timers_is_registered(PyObject *UNUSED(self), PyObject *function) +{ + bool ret = BLI_timer_is_registered((intptr_t)function); + return PyBool_FromLong(ret); +} + + +static struct PyMethodDef M_AppTimers_methods[] = { + {"register", (PyCFunction)bpy_app_timers_register, + METH_VARARGS | METH_KEYWORDS, bpy_app_timers_register_doc}, + {"unregister", (PyCFunction)bpy_app_timers_unregister, + METH_O, bpy_app_timers_unregister_doc}, + {"is_registered", (PyCFunction)bpy_app_timers_is_registered, + METH_O, bpy_app_timers_is_registered_doc}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef M_AppTimers_module_def = { + PyModuleDef_HEAD_INIT, + "bpy.app.timers", /* m_name */ + NULL, /* m_doc */ + 0, /* m_size */ + M_AppTimers_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; + +PyObject *BPY_app_timers_module(void) +{ + PyObject *sys_modules = PyImport_GetModuleDict(); + PyObject *mod = PyModule_Create(&M_AppTimers_module_def); + PyDict_SetItem(sys_modules, PyModule_GetNameObject(mod), mod); + return mod; +} diff --git a/source/blender/python/intern/bpy_app_timers.h b/source/blender/python/intern/bpy_app_timers.h new file mode 100644 index 00000000000..ddf0e258e67 --- /dev/null +++ b/source/blender/python/intern/bpy_app_timers.h @@ -0,0 +1,30 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_app_timers.h + * \ingroup pythonintern + */ + +#ifndef __BPY_APP_TIMERS_H__ +#define __BPY_APP_TIMERS_H__ + +PyObject *BPY_app_timers_module(void); + +#endif /* __BPY_APP_TIMERS_H__ */ diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 576f5470069..fc2832ee4cb 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -51,6 +51,7 @@ #include "BLI_dynstr.h" #include "BLI_utildefines.h" #include "BLI_math.h" +#include "BLI_timer.h" #include "BKE_context.h" #include "BKE_idprop.h" @@ -372,6 +373,8 @@ void wm_event_do_notifiers(bContext *C) if (wm == NULL) return; + BLI_timer_execute(); + /* disable? - keep for now since its used for window level notifiers. */ #if 1 /* cache & catch WM level notifiers, such as frame change, scene/screen set */ diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index e6114b44221..9fca91d2f3f 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -54,6 +54,7 @@ #include "BLI_string.h" #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLI_timer.h" #include "BLO_writefile.h" #include "BLO_undofile.h" @@ -460,6 +461,8 @@ void WM_exit_ext(bContext *C, const bool do_python) } } + BLI_timer_free(); + WM_paneltype_clear(); BKE_addon_pref_type_free(); |