diff --git a/kitty/data-types.c b/kitty/data-types.c index a64b87d8e..c8d68b89c 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -11,6 +11,7 @@ extern int init_Cursor(PyObject *); extern int init_Line(PyObject *); extern int init_ColorProfile(PyObject *); extern int init_SpriteMap(PyObject *); +extern int init_ChangeTracker(PyObject *); extern PyObject* create_256_color_table(); #include "gl.h" @@ -41,6 +42,7 @@ PyInit_fast_data_types(void) { if (!init_Cursor(m)) return NULL; if (!init_ColorProfile(m)) return NULL; if (!init_SpriteMap(m)) return NULL; + if (!init_ChangeTracker(m)) return NULL; if (!add_module_gl_constants(m)) return NULL; PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT); PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); diff --git a/kitty/data-types.h b/kitty/data-types.h index 2aea8c2d5..cf90672c1 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -178,6 +178,18 @@ typedef struct { } SpriteMap; +typedef struct { + PyObject_HEAD + + index_type xnum, ynum; + bool screen_changed; + bool cursor_changed; + bool dirty; + bool *changed_lines; + bool *lines_with_changed_cells; + bool *changed_cells; + unsigned int history_line_added_count; +} ChangeTracker; Line* alloc_line(); Cursor* alloc_cursor(); diff --git a/kitty/tracker.c b/kitty/tracker.c new file mode 100644 index 000000000..b2139f2ad --- /dev/null +++ b/kitty/tracker.c @@ -0,0 +1,252 @@ +/* + * tracker.c + * Copyright (C) 2016 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "data-types.h" +#include + +#define RESET_STATE_VARS(self) \ + self->screen_changed = false; self->cursor_changed = false; self->dirty = false; self->history_line_added_count = 0; + +static PyObject* +resize(ChangeTracker *self, PyObject *args) { +#define resize_doc "Resize theis change tracker must be called when the screen it is tracking for is resized" + unsigned long ynum, xnum; + if (!PyArg_ParseTuple(args, "kk", &ynum, &xnum)) return NULL; + self->ynum = ynum; self->xnum = xnum; +#define ALLOC_VAR(name, sz) \ + bool *name = PyMem_Calloc(sz, sizeof(bool)); \ + if (name == NULL) return PyErr_NoMemory(); \ + PyMem_Free(self->name); self->name = name; + + ALLOC_VAR(changed_lines, self->ynum); + ALLOC_VAR(changed_cells, self->xnum * self->ynum); + ALLOC_VAR(lines_with_changed_cells, self->ynum); + RESET_STATE_VARS(self); + Py_RETURN_NONE; +} + +static PyObject* +new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { + ChangeTracker *self; + self = (ChangeTracker *)type->tp_alloc(type, 0); + if (self != NULL) { + PyObject *ret = resize(self, args); + if (ret == NULL) { Py_CLEAR(self); return NULL; } + Py_CLEAR(ret); + } + return (PyObject*) self; +} + +static void +dealloc(ChangeTracker* self) { + PyMem_Free(self->changed_lines); PyMem_Free(self->changed_cells); PyMem_Free(self->lines_with_changed_cells); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject* +reset(ChangeTracker *self) { +#define reset_doc "Reset all changes" + self->screen_changed = false; self->cursor_changed = false; self->dirty = false; + self->history_line_added_count = 0; + memset(self->changed_lines, 0, self->ynum * sizeof(bool)); + memset(self->changed_cells, 0, self->ynum * self->xnum * sizeof(bool)); + memset(self->lines_with_changed_cells, 0, self->ynum * sizeof(bool)); + RESET_STATE_VARS(self); + + Py_RETURN_NONE; +} + +static inline void tracker_cursor_changed(ChangeTracker *self) { + self->cursor_changed = true; + self->dirty = true; +} + +static inline void tracker_line_added_to_history(ChangeTracker *self) { + self->history_line_added_count++; + self->dirty = true; +} + +static inline void tracker_update_screen(ChangeTracker *self) { + self->screen_changed = true; + self->dirty = true; +} + +static inline void tracker_update_line_range(ChangeTracker *self, unsigned int first_line, unsigned int last_line) { + if (!self->screen_changed) { + for (unsigned int i = first_line; i <= MIN(self->ynum - 1, last_line); i++) self->changed_lines[i] = true; + self->dirty = true; + } +} + +static inline void tracker_update_cell_range(ChangeTracker *self, unsigned int line, unsigned int first_cell, unsigned int last_cell) { + if (!self->screen_changed && line < self->ynum && !self->changed_lines[line]) { + self->lines_with_changed_cells[line] = true; + unsigned int base = line * self->xnum; + for (unsigned int i = first_cell; i <= MIN(self->xnum - 1, last_cell); i++) self->changed_cells[base + i] = true; + self->dirty = true; + } +} + +static PyObject* +cursor_changed(ChangeTracker *self) { +#define cursor_changed_doc "" + tracker_cursor_changed(self); + Py_RETURN_NONE; +} + +static PyObject* +line_added_to_history(ChangeTracker *self) { +#define line_added_to_history_doc "" + tracker_line_added_to_history(self); + Py_RETURN_NONE; +} + +static PyObject* +update_screen(ChangeTracker *self) { +#define update_screen_doc "" + tracker_update_screen(self); + Py_RETURN_NONE; +} + +static PyObject* +update_line_range(ChangeTracker *self, PyObject *args) { +#define update_line_range_doc "" + unsigned int f, l; + if (!PyArg_ParseTuple(args, "II", &f, &l)) return NULL; + tracker_update_line_range(self, f, l); + Py_RETURN_NONE; +} + +static PyObject* +update_cell_range(ChangeTracker *self, PyObject *args) { +#define update_cell_range_doc "" + unsigned int line, f, l; + if (!PyArg_ParseTuple(args, "III", &line, &f, &l)) return NULL; + tracker_update_cell_range(self, line, f, l); + Py_RETURN_NONE; +} + +static inline PyObject* +get_ranges(bool *line, unsigned int xnum) { + PyObject *ans = PyList_New(0), *t; + Py_ssize_t start = -1; + if (ans == NULL) return PyErr_NoMemory(); + +#define APPEND_RANGE(x) \ + t = Py_BuildValue("nI", start, x); \ + if (t == NULL) { Py_CLEAR(ans); return NULL; } \ + if (PyList_Append(ans, t) != 0) { Py_CLEAR(ans); Py_CLEAR(t); return NULL; } \ + Py_CLEAR(t); + + for (unsigned int i = 0; i < xnum; i++) { + if (line[i]) { + if (start == -1) { + start = i; + } + } else { + if (start != -1) { + APPEND_RANGE(i - 1); + start = -1; + } + } + } + if (start != -1) { + APPEND_RANGE(xnum - 1); + } + + return ans; +} + +static PyObject* +consolidate_changes(ChangeTracker *self) { +#define consolidate_changes_doc "" + PyObject *ans = PyDict_New(); + if (ans == NULL) return PyErr_NoMemory(); + if (PyDict_SetItemString(ans, "screen", self->screen_changed ? Py_True : Py_False) != 0) { Py_CLEAR(ans); return NULL; } + if (PyDict_SetItemString(ans, "cursor", self->cursor_changed ? Py_True : Py_False) != 0) { Py_CLEAR(ans); return NULL; } + PyObject *t = PyLong_FromUnsignedLong((unsigned long)self->history_line_added_count); + if (t == NULL) { Py_CLEAR(ans); return NULL; } + if (PyDict_SetItemString(ans, "history_line_added_count", t) != 0) { Py_CLEAR(t); Py_CLEAR(ans); return NULL; } + Py_CLEAR(t); + + // Changed lines + Py_ssize_t num = 0; + if (!self->screen_changed) { + for (unsigned int i = 0; i < self->ynum; i++) { if (self->changed_lines[i]) num++; } + } + t = PyTuple_New(num); + if (t == NULL) { Py_CLEAR(ans); return NULL; } + if (!self->screen_changed) { + for (unsigned int i = 0, j=0; i < self->ynum; i++, j++) { + if (self->changed_lines[i]) { + PyObject *n = PyLong_FromUnsignedLong(i); + if (n == NULL) { Py_CLEAR(t); Py_CLEAR(ans); return NULL; } + PyTuple_SET_ITEM(t, j, n); + } + } + } + if (PyDict_SetItemString(ans, "lines", t) != 0) { Py_CLEAR(t); Py_CLEAR(ans); return NULL; } + Py_CLEAR(t); + + // Changed cells + t = PyDict_New(); + if (t == NULL) { Py_CLEAR(ans); return PyErr_NoMemory(); } + if (!self->screen_changed) { + for (unsigned int i = 0; i < self->ynum; i++) { + if (self->lines_with_changed_cells[i] && !self->changed_lines[i]) { + PyObject *ranges = get_ranges(self->changed_cells + i * self->xnum, self->xnum); + if (ranges == NULL) { Py_CLEAR(t); Py_CLEAR(ans); return NULL; } + PyObject *key = PyLong_FromUnsignedLong(i); + if (key == NULL) { Py_CLEAR(t); Py_CLEAR(ans); Py_CLEAR(ranges); return NULL; } + if (PyDict_SetItem(t, key, ranges) != 0) { Py_CLEAR(key); Py_CLEAR(t); Py_CLEAR(ans); Py_CLEAR(ranges); return NULL; } + Py_CLEAR(key); Py_CLEAR(ranges); + } + } + } + + if (PyDict_SetItemString(ans, "cells", t) != 0) { Py_CLEAR(t); Py_CLEAR(ans); return NULL; } + Py_CLEAR(t); + + + return ans; +} + +// Boilerplate {{{ + +BOOL_GETSET(ChangeTracker, dirty) +static PyGetSetDef getseters[] = { + GETSET(dirty) + {NULL} /* Sentinel */ +}; + +static PyMethodDef methods[] = { + METHOD(resize, METH_VARARGS) + METHOD(reset, METH_NOARGS) + METHOD(cursor_changed, METH_NOARGS) + METHOD(consolidate_changes, METH_NOARGS) + METHOD(line_added_to_history, METH_NOARGS) + METHOD(update_screen, METH_NOARGS) + METHOD(update_line_range, METH_VARARGS) + METHOD(update_cell_range, METH_VARARGS) + {NULL} /* Sentinel */ +}; + + +static PyTypeObject ChangeTracker_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "fast_data_types.ChangeTracker", + .tp_basicsize = sizeof(ChangeTracker), + .tp_dealloc = (destructor)dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "ChangeTracker", + .tp_methods = methods, + .tp_getset = getseters, + .tp_new = new, +}; + +INIT_TYPE(ChangeTracker) +// }}} diff --git a/setup.py b/setup.py index f7c88f492..49c603c5c 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,7 @@ def main(): compile_c_extension( 'kitty/fast_data_types', 'kitty/line.c', 'kitty/data-types.c', 'kitty/line-buf.c', 'kitty/cursor.c', 'kitty/colors.c', - 'kitty/sprites.c') + 'kitty/sprites.c', 'kitty/tracker.c') elif args.action == 'test': os.execlp(sys.executable, sys.executable, os.path.join(base, 'test.py'))