mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-09 23:54:20 +02:00
Move the timers implementation to C
It is used int he hot loop for monitoring the child process
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 *);
|
||||
|
||||
@@ -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
177
kitty/timers.c
Normal 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)
|
||||
// }}}
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user