From 9b73d25151abdb3c213a233b7d97342fe2cacdc0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Jan 2019 18:37:29 +0530 Subject: [PATCH] Add infrastructure for timers to the event loop --- kitty/child-monitor.c | 25 ++++++++++ kitty/timers.c | 110 ++++++++++++++++++++++++++++++++++++++++++ kitty/timers.h | 33 +++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 kitty/timers.c create mode 100644 kitty/timers.h diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 4cd29d16e..b816865c7 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -10,6 +10,7 @@ #include "screen.h" #include "fonts.h" #include "charsets.h" +#include "timers.h" #include #include #include @@ -727,12 +728,35 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) { Py_RETURN_NONE; } +static EventLoopData main_event_loop = {0}; + static inline void wait_for_events() { + maximum_wait = prepare_for_poll(&main_event_loop, maximum_wait); event_loop_wait(maximum_wait); + dispatch_timers(&main_event_loop); maximum_wait = -1; } +static void +python_timer_callback(id_type timer_id, void *data) { + PyObject *callback = (PyObject*)data; + unsigned long long id = timer_id; + PyObject *ret = PyObject_CallFunction(callback, "K", id); + if (ret == NULL) PyErr_Print(); + else Py_DECREF(ret); +} + +static PyObject* +add_python_timer(PyObject *self UNUSED, PyObject *args) { + PyObject *callback; + double interval; + const char *name; + if (!PyArg_ParseTuple(args, "sOd", &name, &callback, &interval)) return NULL; + unsigned long long timer_id = add_timer(&main_event_loop, name, interval, 1, python_timer_callback, callback); + return Py_BuildValue("K", timer_id); +} + static inline void process_pending_resizes(double now) { global_state.has_pending_resizes = false; @@ -1379,6 +1403,7 @@ static PyMethodDef methods[] = { METHOD(main_loop, METH_NOARGS) METHOD(mark_for_close, METH_VARARGS) METHOD(resize_pty, METH_VARARGS) + METHODB(add_python_timer, METH_VARARGS), {"set_iutf8", (PyCFunction)pyset_iutf8, METH_VARARGS, ""}, {NULL} /* Sentinel */ }; diff --git a/kitty/timers.c b/kitty/timers.c new file mode 100644 index 000000000..1b8e646a6 --- /dev/null +++ b/kitty/timers.c @@ -0,0 +1,110 @@ +/* + * timers.c + * Copyright (C) 2019 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "timers.h" +#include + +static id_type timer_counter = 0; + +static int +compare_timers(const void *a_, const void *b_) { + const Timer *a = (const Timer*)a_, *b = (const Timer*)b_; + return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0; +} + +static inline void +update_timers(EventLoopData *eld) { + if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers); +} + +id_type +add_timer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data) { + if (eld->timers_count >= sizeof(eld->timers)/sizeof(eld->timers[0])) { + fprintf(stderr, "Too many timers added\n"); + return 0; + } + Timer *t = eld->timers + eld->timers_count++; + t->interval = interval; + t->name = name; + t->trigger_at = enabled ? monotonic() + interval : DBL_MAX; + t->callback = cb; + t->callback_data = cb_data; + t->id = ++timer_counter; + update_timers(eld); + return t->id; +} + +#define removeX(which, item_id, update_func) {\ + for (nfds_t i = 0; i < eld->which##_count; i++) { \ + if (eld->which[i].id == item_id) { \ + eld->which##_count--; \ + if (i < eld->which##_count) { \ + memmove(eld->which + i, eld->which + i + 1, sizeof(eld->which[0]) * (eld->which##_count - i)); \ + } \ + update_func(eld); break; \ +}}} + +void +remove_timer(EventLoopData *eld, id_type timer_id) { + removeX(timers, timer_id, update_timers); +} + +void +toggle_timer(EventLoopData *eld, id_type timer_id, int enabled) { + for (nfds_t i = 0; i < eld->timers_count; i++) { + if (eld->timers[i].id == timer_id) { + double trigger_at = enabled ? (monotonic() + eld->timers[i].interval) : DBL_MAX; + if (trigger_at != eld->timers[i].trigger_at) { + eld->timers[i].trigger_at = trigger_at; + update_timers(eld); + } + break; + } + } +} + +void +change_timer_interval(EventLoopData *eld, id_type timer_id, double interval) { + for (nfds_t i = 0; i < eld->timers_count; i++) { + if (eld->timers[i].id == timer_id) { + eld->timers[i].interval = interval; + break; + } + } +} + + +double +prepare_for_poll(EventLoopData *eld, double timeout) { + if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return timeout; + double now = monotonic(), next_repeat_at = eld->timers[0].trigger_at; + if (timeout < 0 || now + timeout > next_repeat_at) { + timeout = next_repeat_at <= now ? 0 : next_repeat_at - now; + } + return timeout; +} + +unsigned int +dispatch_timers(EventLoopData *eld) { + if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return 0; + static struct { timer_callback_func func; id_type id; void* data; } dispatches[sizeof(eld->timers)/sizeof(eld->timers[0])]; + unsigned int num_dispatches = 0; + double now = monotonic(); + for (nfds_t i = 0; i < eld->timers_count && eld->timers[i].trigger_at <= now; i++) { + eld->timers[i].trigger_at = now + eld->timers[i].interval; + dispatches[num_dispatches].func = eld->timers[i].callback; + dispatches[num_dispatches].id = eld->timers[i].id; + dispatches[num_dispatches].data = eld->timers[i].callback_data; + num_dispatches++; + } + // we dispatch separately so that the callbacks can modify timers + for (unsigned i = 0; i < num_dispatches; i++) { + dispatches[i].func(dispatches[i].id, dispatches[i].data); + } + if (num_dispatches) update_timers(eld); + return num_dispatches; +} diff --git a/kitty/timers.h b/kitty/timers.h new file mode 100644 index 000000000..cd47cdb9e --- /dev/null +++ b/kitty/timers.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + +#include "data-types.h" + +typedef void(*timer_callback_func)(id_type, void*); + +typedef struct { + id_type id; + double interval, trigger_at; + timer_callback_func callback; + void *callback_data; + const char *name; +} Timer; + + +typedef struct { + nfds_t timers_count; + Timer timers[128]; +} EventLoopData; + + +double prepare_for_poll(EventLoopData *eld, double timeout); +id_type add_timer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data); +void remove_timer(EventLoopData *eld, id_type timer_id); +void toggle_time(EventLoopData *eld, id_type timer_id, int enabled); +void change_timer_interval(EventLoopData *eld, id_type timer_id, double interval); +unsigned int dispatch_timers(EventLoopData *eld);