mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-25 17:52:02 +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 (
|
from .fast_data_types import (
|
||||||
GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
|
GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
|
||||||
GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT,
|
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 .fonts.render import set_font_family
|
||||||
from .keys import (
|
from .keys import (
|
||||||
@@ -33,7 +33,6 @@ from .keys import (
|
|||||||
from .session import create_session
|
from .session import create_session
|
||||||
from .shaders import Sprites
|
from .shaders import Sprites
|
||||||
from .tabs import SpecialWindow, TabManager
|
from .tabs import SpecialWindow, TabManager
|
||||||
from .timers import Timers
|
|
||||||
from .utils import handle_unix_signals, pipe2, safe_print
|
from .utils import handle_unix_signals, pipe2, safe_print
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
@@ -203,7 +202,7 @@ class Boss(Thread):
|
|||||||
self.read_dispatch_map[r]()
|
self.read_dispatch_map[r]()
|
||||||
for w in writers:
|
for w in writers:
|
||||||
self.write_dispatch_map[w]()
|
self.write_dispatch_map[w]()
|
||||||
self.timers()
|
self.timers.call()
|
||||||
for w in self.iterwindows():
|
for w in self.iterwindows():
|
||||||
if w.screen.is_dirty():
|
if w.screen.is_dirty():
|
||||||
self.timers.add_if_missing(
|
self.timers.add_if_missing(
|
||||||
@@ -214,7 +213,7 @@ class Boss(Thread):
|
|||||||
# debounce resize events
|
# debounce resize events
|
||||||
self.pending_resize = True
|
self.pending_resize = True
|
||||||
yield
|
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):
|
def apply_pending_resize(self, w, h):
|
||||||
if w > 100 and h > 100:
|
if w > 100 and h > 100:
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ PyInit_fast_data_types(void) {
|
|||||||
if (!init_HistoryBuf(m)) return NULL;
|
if (!init_HistoryBuf(m)) return NULL;
|
||||||
if (!init_Line(m)) return NULL;
|
if (!init_Line(m)) return NULL;
|
||||||
if (!init_Cursor(m)) return NULL;
|
if (!init_Cursor(m)) return NULL;
|
||||||
|
if (!init_Timers(m)) return NULL;
|
||||||
if (!init_ColorProfile(m)) return NULL;
|
if (!init_ColorProfile(m)) return NULL;
|
||||||
if (!init_SpriteMap(m)) return NULL;
|
if (!init_SpriteMap(m)) return NULL;
|
||||||
if (!init_ChangeTracker(m)) return NULL;
|
if (!init_ChangeTracker(m)) return NULL;
|
||||||
|
|||||||
@@ -299,6 +299,21 @@ typedef struct {
|
|||||||
} Screen;
|
} Screen;
|
||||||
PyTypeObject Screen_Type;
|
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) \
|
#define left_shift_line(line, at, num) \
|
||||||
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
|
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
|
||||||
COPY_CELL(line, __i__ + (num), line, __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_LineBuf(PyObject *);
|
||||||
int init_HistoryBuf(PyObject *);
|
int init_HistoryBuf(PyObject *);
|
||||||
int init_Cursor(PyObject *);
|
int init_Cursor(PyObject *);
|
||||||
|
int init_Timers(PyObject *);
|
||||||
int init_Line(PyObject *);
|
int init_Line(PyObject *);
|
||||||
int init_ColorProfile(PyObject *);
|
int init_ColorProfile(PyObject *);
|
||||||
int init_SpriteMap(PyObject *);
|
int init_SpriteMap(PyObject *);
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ def dispatch_pending_calls(boss):
|
|||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
safe_print(traceback.format_exc())
|
safe_print(traceback.format_exc())
|
||||||
boss.ui_timers()
|
boss.ui_timers.call()
|
||||||
|
|
||||||
|
|
||||||
def run_app(opts, args):
|
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