diff --git a/kitty/data-types.h b/kitty/data-types.h index a3d30eefc..b179c13f8 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -110,11 +110,15 @@ typedef unsigned int index_type; #define GETSET(x) \ {#x, (getter) x##_get, (setter) x##_set, #x, NULL}, +#ifndef EXTRA_INIT +#define EXTRA_INIT +#endif #define INIT_TYPE(type) \ int init_##type(PyObject *module) {\ if (PyType_Ready(&type##_Type) < 0) return 0; \ if (PyModule_AddObject(module, #type, (PyObject *)&type##_Type) != 0) return 0; \ Py_INCREF(&type##_Type); \ + EXTRA_INIT; \ return 1; \ } diff --git a/kitty/timers.c b/kitty/timers.c index 47563c926..c509b9c63 100644 --- a/kitty/timers.c +++ b/kitty/timers.c @@ -5,9 +5,47 @@ * Distributed under terms of the GPL3 license. */ +#ifdef __APPLE__ +#define EXTRA_INIT mach_timebase_info(&timebase); +#endif #include "data-types.h" #include -#include +/* To millisecond (10^-3) */ +#define SEC_TO_MS 1000 + +/* To microseconds (10^-6) */ +#define MS_TO_US 1000 +#define SEC_TO_US (SEC_TO_MS * MS_TO_US) + +/* To nanoseconds (10^-9) */ +#define US_TO_NS 1000 +#define MS_TO_NS (MS_TO_US * US_TO_NS) +#define SEC_TO_NS (SEC_TO_MS * MS_TO_NS) + +/* Conversion from nanoseconds */ +#define NS_TO_MS (1000 * 1000) +#define NS_TO_US (1000) + +#ifdef __APPLE__ +#include +static mach_timebase_info_data_t timebase = {0}; +static inline double monotonic() { + return ((double)(mach_absolute_time() * timebase.numer) / timebase.denom)/SEC_TO_NS; +} +#else +#include +static inline double monotonic() { + struct timespec ts = {0}; +#ifdef CLOCK_HIGHRES + clock_gettime(CLOCK_HIGHRES, &ts); +#elif CLOCK_MONOTONIC_RAW + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +#else + clock_gettime(CLOCK_MONOTONIC, &ts); +#endif + return (((double)ts.tv_nsec) / SEC_TO_NS) + (double)ts.tv_sec; +} +#endif static PyObject * new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { @@ -29,7 +67,7 @@ dealloc(Timers* self) { for (size_t i = 0; i < self->count; i++) { Py_CLEAR(self->events[i].callback); Py_CLEAR(self->events[i].args); } - PyMem_Free(self->events); self->events = NULL; + PyMem_Free(self->buf1); self->events = NULL; } Py_TYPE(self)->tp_free((PyObject*)self); } @@ -65,7 +103,7 @@ add(Timers *self, PyObject *fargs) { PyObject *callback, *args = NULL; double delay, at; if (!PyArg_ParseTuple(fargs, "dO|O", &delay, &callback, &args)) return NULL; - at = glfwGetTime() + delay; + at = monotonic() + delay; for (i = 0; i < self->count; i++) { if (self->events[i].callback == callback) { @@ -96,7 +134,7 @@ add_if_missing(Timers *self, PyObject *fargs) { Py_RETURN_NONE; } } - at = glfwGetTime() + delay; + at = monotonic() + delay; return _add(self, at, callback, args); } @@ -123,7 +161,7 @@ static PyObject * timeout(Timers *self) { #define timeout_doc "timeout() -> The time in seconds until the next event" if (self->count < 1) { Py_RETURN_NONE; } - double ans = self->events[0].at - glfwGetTime(); + double ans = self->events[0].at - monotonic(); return PyFloat_FromDouble(MAX(0, ans)); } @@ -132,7 +170,7 @@ call(Timers *self) { #define call_doc "call() -> Dispatch all expired events" if (self->count < 1) { Py_RETURN_NONE; } TimerEvent *other = self->events == self->buf1 ? self->buf2 : self->buf1; - double now = glfwGetTime(); + double now = monotonic(); size_t i, j; for (i = 0, j = 0; i < self->count; i++) { if (self->events[i].at <= now) { // expired, call it diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 018ba7058..ee36dfbe9 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -5,10 +5,13 @@ import os from unittest import skipIf -from . import BaseTest, filled_line_buf, filled_cursor, filled_history_buf from kitty.config import build_ansi_color_table, defaults -from kitty.utils import wcwidth, sanitize_title -from kitty.fast_data_types import LineBuf, Cursor as C, REVERSE, ColorProfile, SpriteMap, HistoryBuf, Cursor +from kitty.fast_data_types import ( + REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf, SpriteMap, Timers +) +from kitty.utils import sanitize_title, wcwidth + +from . import BaseTest, filled_cursor, filled_history_buf, filled_line_buf def create_lbuf(*lines): @@ -340,7 +343,7 @@ class TestDataTypes(BaseTest): lb.as_ansi(a.append) self.ae(a, ['\x1b[0m' + str(lb.line(i)) + '\n' for i in range(lb.ynum)]) l = lb.line(0) - c = Cursor() + c = C() c.bold = c.italic = c.reverse = c.strikethrough = True c.fg = (4 << 8) | 1 c.bg = (1 << 24) | (2 << 16) | (3 << 8) | 2 @@ -358,3 +361,23 @@ class TestDataTypes(BaseTest): a = [] hb.as_ansi(a.append) self.ae(a, ['\x1b[0m' + str(hb.line(i)) + '\n' for i in range(hb.count - 1, -1, -1)]) + + def test_timers(self): + t = Timers() + a = [] + t.add(10, a.append, (1,)) + t.add(0, a.append, (2,)) + t.add_if_missing(20, a.append, (3,)) + self.ae(t.timeout(), 0) + t.call(), t.call() + self.ae(a, [2]) + del a[:] + t.add(200, a.append, (4,)) + d = t.timeout() + self.assertGreater(d, 8) + self.assertLess(d, 10.1) + t.call() + self.ae(a, []) + t.add(0, a.append, (5,)) + t.call() + self.ae(a, [5])