Move the timers implementation to C

It is used int he hot loop for monitoring the child process
This commit is contained in:
Kovid Goyal
2017-08-26 17:30:20 +05:30
parent 2de0c82f18
commit f9b1a71edd
6 changed files with 198 additions and 63 deletions

View File

@@ -24,7 +24,7 @@ from .constants import (
from .fast_data_types import (
GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT,
drain_read, glBlendFunc, glfw_post_empty_event, glViewport
drain_read, glBlendFunc, glfw_post_empty_event, glViewport, Timers
)
from .fonts.render import set_font_family
from .keys import (
@@ -33,7 +33,6 @@ from .keys import (
from .session import create_session
from .shaders import Sprites
from .tabs import SpecialWindow, TabManager
from .timers import Timers
from .utils import handle_unix_signals, pipe2, safe_print
if isosx:
@@ -203,7 +202,7 @@ class Boss(Thread):
self.read_dispatch_map[r]()
for w in writers:
self.write_dispatch_map[w]()
self.timers()
self.timers.call()
for w in self.iterwindows():
if w.screen.is_dirty():
self.timers.add_if_missing(
@@ -214,7 +213,7 @@ class Boss(Thread):
# debounce resize events
self.pending_resize = True
yield
self.timers.add(0.02, self.apply_pending_resize, w, h)
self.timers.add(0.02, self.apply_pending_resize, (w, h))
def apply_pending_resize(self, w, h):
if w > 100 and h > 100:

View File

@@ -110,6 +110,7 @@ PyInit_fast_data_types(void) {
if (!init_HistoryBuf(m)) return NULL;
if (!init_Line(m)) return NULL;
if (!init_Cursor(m)) return NULL;
if (!init_Timers(m)) return NULL;
if (!init_ColorProfile(m)) return NULL;
if (!init_SpriteMap(m)) return NULL;
if (!init_ChangeTracker(m)) return NULL;

View File

@@ -299,6 +299,21 @@ typedef struct {
} Screen;
PyTypeObject Screen_Type;
typedef struct {
double at;
PyObject *callback;
PyObject *args;
} TimerEvent;
typedef struct {
PyObject_HEAD
TimerEvent *events, *buf1, *buf2;
size_t capacity;
size_t count;
} Timers;
PyTypeObject Timers_Type;
#define left_shift_line(line, at, num) \
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
COPY_CELL(line, __i__ + (num), line, __i__) \
@@ -315,6 +330,7 @@ ChangeTracker* alloc_change_tracker(unsigned int, unsigned int);
int init_LineBuf(PyObject *);
int init_HistoryBuf(PyObject *);
int init_Cursor(PyObject *);
int init_Timers(PyObject *);
int init_Line(PyObject *);
int init_ColorProfile(PyObject *);
int init_SpriteMap(PyObject *);

View File

@@ -178,7 +178,7 @@ def dispatch_pending_calls(boss):
except Exception:
import traceback
safe_print(traceback.format_exc())
boss.ui_timers()
boss.ui_timers.call()
def run_app(opts, args):

177
kitty/timers.c Normal file
View File

@@ -0,0 +1,177 @@
/*
* timers.c
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <stdlib.h>
#include <GLFW/glfw3.h>
static PyObject *
new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
Timers *self;
self = (Timers *)type->tp_alloc(type, 0);
self->capacity = 1024;
self->count = 0;
self->buf1 = (TimerEvent*)PyMem_Calloc(2 * self->capacity, sizeof(TimerEvent));
if (self->buf1 == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); }
self->events = self->buf1;
self->buf2 = self->buf1 + self->capacity;
return (PyObject*) self;
}
static void
dealloc(Timers* self) {
if (self->events) {
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;
}
Py_TYPE(self)->tp_free((PyObject*)self);
}
static int
compare_events(const void *a, const void *b) {
double av = ((TimerEvent*)(a))->at, bv = ((TimerEvent*)(b))->at;
return av > bv ? 1 : (av == bv ? 0 : -1);
}
static inline PyObject*
_add(Timers *self, double at, PyObject *callback, PyObject *args) {
size_t i;
if (self->count >= self->capacity) {
PyErr_SetString(PyExc_ValueError, "Too many timers");
return NULL;
}
i = self->count++;
self->events[i].at = at; self->events[i].callback = callback; self->events[i].args = args;
Py_INCREF(callback); Py_XINCREF(args);
qsort(self->events, self->count, sizeof(TimerEvent), compare_events);
Py_RETURN_NONE;
}
static PyObject *
add(Timers *self, PyObject *fargs) {
#define add_doc "add(delay, callback, args) -> Add callback, replacing it if it already exists"
size_t i;
bool added = false;
PyObject *callback, *args = NULL;
double delay, at;
if (!PyArg_ParseTuple(fargs, "dO|O", &delay, &callback, &args)) return NULL;
at = glfwGetTime() + delay;
for (i = 0; i < self->count; i++) {
if (self->events[i].callback == callback) {
added = true;
self->events[i].at = at;
Py_CLEAR(self->events[i].args);
self->events[i].args = args;
Py_XINCREF(args);
break;
}
}
if (!added) return _add(self, at, callback, args);
Py_RETURN_NONE;
}
static PyObject *
add_if_missing(Timers *self, PyObject *fargs) {
#define add_if_missing_doc "add_if_missing(delay, callback, args) -> Add callback, unless it already exists"
size_t i;
PyObject *callback, *args = NULL;
double delay, at;
if (!PyArg_ParseTuple(fargs, "dO|O", &delay, &callback, &args)) return NULL;
for (i = 0; i < self->count; i++) {
if (self->events[i].callback == callback) {
Py_RETURN_NONE;
}
}
at = glfwGetTime() + delay;
return _add(self, at, callback, args);
}
static PyObject *
remove_event(Timers *self, PyObject *callback) {
#define remove_event_doc "remove(callback) -> Remove the event with the specified callback, if present"
TimerEvent *other = self->events == self->buf1 ? self->buf2 : self->buf1;
size_t i, j;
for (i = 0, j = 0; i < self->count; i++) {
if (self->events[i].callback != callback) {
other[j].callback = self->events[i].callback; other[j].at = self->events[i].at; other[j].args = self->events[i].args;
j++;
} else {
Py_CLEAR(self->events[i].callback); Py_CLEAR(self->events[i].args);
}
}
self->events = other;
self->count = j;
Py_RETURN_NONE;
}
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();
return PyFloat_FromDouble(MAX(0, ans));
}
static PyObject *
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();
size_t i, j;
for (i = 0, j = 0; i < self->count; i++) {
if (self->events[i].at <= now) { // expired, call it
PyObject *ret = PyObject_CallObject(self->events[i].callback, self->events[i].args);
Py_CLEAR(self->events[i].callback); Py_CLEAR(self->events[i].args);
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
} else {
other[j].callback = self->events[i].callback; other[j].at = self->events[i].at; other[j].args = self->events[i].args;
j++;
}
}
self->events = other;
self->count = j;
Py_RETURN_NONE;
}
// Boilerplate {{{
static PyMethodDef methods[] = {
METHOD(add, METH_VARARGS)
METHOD(add_if_missing, METH_VARARGS)
METHOD(remove_event, METH_O)
METHOD(timeout, METH_NOARGS)
METHOD(call, METH_NOARGS)
{NULL} /* Sentinel */
};
PyTypeObject Timers_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.Timers",
.tp_basicsize = sizeof(Timers),
.tp_dealloc = (destructor)dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "Timers",
.tp_methods = methods,
.tp_new = new,
};
INIT_TYPE(Timers)
// }}}

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from collections import namedtuple
from operator import itemgetter
from time import monotonic
from .utils import safe_print
Event = namedtuple('Event', 'at callback args')
get_at = itemgetter(0)
class Timers:
def __init__(self):
self.timers = []
def _add(self, delay, callback, args):
self.timers.append(Event(monotonic() + delay, callback, args))
self.timers.sort(key=get_at)
def add(self, delay, callback, *args):
self.remove(callback)
self._add(delay, callback, args)
def add_if_missing(self, delay, callback, *args):
for ev in self.timers:
if ev.callback == callback:
return
self._add(delay, callback, args)
def remove(self, callback):
for i, ev in enumerate(self.timers):
if ev.callback == callback:
break
else:
return
del self.timers[i]
def timeout(self):
if self.timers:
return max(0, self.timers[0][0] - monotonic())
def __call__(self):
if self.timers:
now = monotonic()
expired_timers, waiting_timers = [], []
for ev in self.timers:
(expired_timers if ev[0] <= now else waiting_timers).append(ev)
self.timers = waiting_timers
for ev in expired_timers:
try:
ev.callback(*ev.args)
except Exception:
import traceback
safe_print(traceback.format_exc())